<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>쿠키의 주저리</title>
    <link>https://insengnewbie.tistory.com/</link>
    <description>무엇이 나를 살아있게 만드는가</description>
    <language>ko</language>
    <pubDate>Sun, 19 Jul 2026 16:22:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>KimCookieYa</managingEditor>
    <image>
      <title>쿠키의 주저리</title>
      <url>https://tistory1.daumcdn.net/tistory/4237357/attach/665541a25fbc4e909a497394b210fc70</url>
      <link>https://insengnewbie.tistory.com</link>
    </image>
    <item>
      <title>센디 SEO 3배 개선기(2) - 페이지를 빠르게 만들기</title>
      <link>https://insengnewbie.tistory.com/654</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서 이어서 진행하겠습니다. 제가 어쩌다가 SEO를 맡게 되었는지 궁금하시다면 1편을 확인해주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://insengnewbie.tistory.com/653&quot;&gt;https://insengnewbie.tistory.com/653&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782706864024&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;센디 SEO 3배 개선기(1) - 어쩌다 SEO를 맡게 되었나&quot; data-og-description=&quot;어느 날, CTO님이 말했습니다&amp;quot;요즘 페이지가 좀 느린 것 같지 않아요?&amp;quot; 슬랙으로 날아온 이 한 문장이 시작이었습니다. 그땐 이게 9개월 뒤 트래픽을 3배로 만드는 여정의 입구일 줄은 몰랐습니다.&quot; data-og-host=&quot;insengnewbie.tistory.com&quot; data-og-source-url=&quot;https://insengnewbie.tistory.com/653&quot; data-og-url=&quot;https://insengnewbie.tistory.com/653&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PcDLF/dJMb85vZDpb/4A01PcIHrxwVk11wmi4KQ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/RVJq4/dJMb8QewO7u/wfnRXCpRJ7ZPgfGYJ0mOF0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b0vroU/dJMb9bwcKb6/qn3Dgy7Zy86A8S6qOmnz0K/img.png?width=908&amp;amp;height=463&amp;amp;face=0_0_908_463&quot;&gt;&lt;a href=&quot;https://insengnewbie.tistory.com/653&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://insengnewbie.tistory.com/653&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PcDLF/dJMb85vZDpb/4A01PcIHrxwVk11wmi4KQ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/RVJq4/dJMb8QewO7u/wfnRXCpRJ7ZPgfGYJ0mOF0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b0vroU/dJMb9bwcKb6/qn3Dgy7Zy86A8S6qOmnz0K/img.png?width=908&amp;amp;height=463&amp;amp;face=0_0_908_463');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디 SEO 3배 개선기(1) - 어쩌다 SEO를 맡게 되었나&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어느 날, CTO님이 말했습니다&quot;요즘 페이지가 좀 느린 것 같지 않아요?&quot; 슬랙으로 날아온 이 한 문장이 시작이었습니다. 그땐 이게 9개월 뒤 트래픽을 3배로 만드는 여정의 입구일 줄은 몰랐습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;insengnewbie.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낌이 아니라 숫자로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 개선에서 가장 먼저 배운 교훈은 &quot;체감으로 고치지 말 것&quot;이었습니다. &quot;느린 것 같다&quot;는 말은 출발점일 뿐, 무엇을 고칠지는 알려주지 않습니다. 그래서 저는 두 개의 지표를 기준점으로 삼았습니다. &lt;b&gt;FCP(First Contentful Paint)&lt;/b&gt;와 &lt;b&gt;LCP(Largest Contentful Paint)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 FCP는 &quot;화면에 뭐라도 처음 그려지는 시점&quot;이고, LCP는 &quot;그 페이지에서 가장 큰 핵심 콘텐츠(보통 히어로 이미지나 큰 텍스트 블록)가 다 그려지는 시점&quot;입니다. 사용자 입장에서 &quot;이 페이지 빠르네/느리네&quot;를 결정짓는 건 사실상 이 두 순간이죠. 그리고 앞 편에서 말했듯, 이 둘은 Google이 보는 Core Web Vitals의 일부이기도 합니다. 성능과 SEO가 만나는 지점이 정확히 여기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석 방법은 단순했습니다. DevTools Performance 탭에서 페이지 로드를 녹화한 뒤, 타임라인을 펼쳐 &quot;무엇이 LCP와 FCP를 뒤로 미루고 있는가&quot;를 하나씩 짚어내는 것이었습니다. 거창한 도구는 필요 없었습니다. 범인을 특정하고, 하나 고치고, 다시 측정하고. 이 지루한 루프의 반복이 전부였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 1: 보내는 양을 줄이다 (번들 다이어트)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;측정을 해보니 가장 먼저 눈에 띈 범인은 &lt;b&gt;자바스크립트 번들&lt;/b&gt;이었습니다. 브라우저가 받아야 할 JS가 많을수록, 그걸 내려받고 파싱하고 실행하는 데 시간이 걸리고, 그만큼 화면이 그려지는 시점(FCP&amp;middot;LCP)도 뒤로 밀립니다. 다이어트의 원칙은 단순합니다. 브라우저에게 보내는 양 자체를 줄이는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Next.js의 렌더링 전략이 큰 무기가 됐습니다. 핵심은 &quot;이 페이지를 굳이 클라이언트에서 그릴 필요가 있나?&quot;를 페이지마다 따져보는 것이었죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내용이 자주 바뀌지 않는 페이지(서비스 소개, 정책 페이지 등)는 &lt;b&gt;SSG/ISR&lt;/b&gt;로 빌드 시점 또는 주기적으로 미리 렌더링 처리하였습니다. 사용자가 들어오면 완성된 HTML이 바로 날아가니, 클라이언트가 처음부터 그릴 때보다 훨씬 빨라집니다.&lt;/li&gt;
&lt;li&gt;매 요청마다 최신 데이터가 필요한 페이지는 &lt;b&gt;SSR&lt;/b&gt;로 서버에서 그려 내려줬습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 쪽이든 공통된 효과는 &quot;클라이언트에서 처리할 일을 서버사이드로 옮긴&quot; 것이었습니다. 서버가 미리 HTML을 만들어 주니, 브라우저는 무거운 JS 묶음을 다 실행할 때까지 기다리지 않고도 콘텐츠를 먼저 보여줄 수 있게 됐죠. 결과적으로 FCP가 눈에 띄게 당겨졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 &quot;어디서 그릴까&quot;의 이야기였다면, 그다음은 &quot;그래도 보내야 하는 JS를 어떻게 더 줄일까&quot;의 문제였습니다. 렌더링 전략을 바꿔도 클라이언트로 가는 번들은 여전히 남아 있었거든요. 그래서 번들 자체를 깎는 작업을 병행했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코드 스플리팅 &amp;amp; dynamic import&lt;/b&gt;: 모든 코드를 한 덩어리로 묶어 내려보내면, 당장 첫 화면에 필요 없는 코드까지 사용자가 기다려야 합니다. 그래서 초기 렌더링에 꼭 필요하지 않은 무거운 컴포넌트(모달, 첫 뷰포트 아래쪽에서야 등장하는 위젯 등)는 &lt;code&gt;next/dynamic&lt;/code&gt;으로 잘라내, 실제로 필요해질 때 따로 불러오도록 했습니다. 첫 화면이 짊어지는 JS의 무게가 그만큼 가벼워졌죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// 첫 화면에 필요 없는 무거운 컴포넌트는 분리해서 나중에 로드
const HeavyModal = dynamic(() =&amp;gt; import('./HeavyModal'), {
  ssr: false, // 클라이언트에서만, 필요할 때 로드
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;불필요한 라이브러리 걷어내기&lt;/b&gt;: 번들을 들여다보니, 고작 함수 하나 쓰자고 통째로 들고 온 무거운 라이브러리들이 보였습니다. 이런 것들은 가벼운 대안으로 교체하거나, 직접 몇 줄로 구현해 의존성 자체를 들어냈습니다. &quot;이 패키지가 정말 그 용량값을 하는가&quot;를 하나씩 따져본 셈이죠.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요한 코드 걷어내기&lt;/b&gt;: knip 라이브러리를 활용하여 불필요하게 존재하는 레거시 함수, 컴포넌트, 파일, 라이브러리를 들어냈습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS만 무거운 게 아니었습니다. CSS와 폰트에도 같은 다이어트를 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS에서는 Tailwind 설정 하나가 발목을 잡고 있었습니다. Tailwind는 빌드할 때 실제로 안 쓰는 클래스를 걷어내(purge) 파일을 얇게 유지하는데, 우리에겐 그 purge를 거스르는 &lt;code&gt;safelist&lt;/code&gt;가 있었거든요. safelist는 코드에서 문자열로 조합해 만드는 클래스명처럼, Tailwind가 &quot;안 쓴다&quot;고 착각해 지워버릴 클래스를 따로 지켜주는 목록입니다. 그런데 이게 너무 헐거웠습니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;safelist: [
  { pattern: /bg-(.*)/, variants: ['md', 'lg', 'xl'] },
  { pattern: /text-(.*)/, variants: ['md', 'lg', 'xl'] },
  { pattern: /accent-(.*)/, variants: ['md', 'lg', 'xl'] },
  // ...
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;bg-(.*)&lt;/code&gt;는 &quot;bg로 시작하는 클래스는 전부 남겨라&quot;라는 뜻입니다. 여기에 md&amp;middot;lg&amp;middot;xl 반응형 변형까지 곱해지니, 쓰지도 않는 유틸리티 클래스가 최종 CSS에 무더기로 박제됐죠. purge를 켜둔 의미가 사라진 겁니다. 그래서 &lt;code&gt;.*&lt;/code&gt; 같은 막연한 패턴을 실제로 동적 생성하는 값만 콕 집어 좁혔더니, CSS 파일이 453KB에서 26KB로 줄었습니다. 안 쓰는 스타일이 그만큼 실려 있었다는 뜻이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트도 사정이 비슷했습니다. 우리가 쓰던 건 굵기 100부터 700까지를 한 파일에 다 담은 Variable 폰트였습니다. 굵기를 자유롭게 조절할 수 있어 편하지만, 정작 화면에서 쓰는 굵기는 몇 개뿐인데 안 쓰는 굵기까지 통째로 내려받고 있었죠. 그래서 Variable 파일을 버리고, 실제로 쓰는 굵기만 담은 Static 파일 몇 개로 쪼갰습니다. 거기에 서브셋(subset)도 적용했습니다. 웹에서 자주 쓰는 한글&amp;middot;영문&amp;middot;기본 기호&amp;middot;통화 기호 정도만 남기고 나머지 글자를 들어낸 변형이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브셋은 공짜가 아닙니다. 체감 로딩이 빨라지는 대신, 남겨두지 않은 글자(중국어&amp;middot;일본어나 특수 기호 등)가 들어오면 깨질 수 있습니다. 그래서 그 폰트가 어디에 쓰이는지 사용 범위가 확실한 곳에만 적용했고, 다국어가 필요한 곳에는 전체 woff2를 그대로 뒀습니다. 결과는 분명했습니다. 단일 폰트 파일 2.1MB가, 굵기별로 쪼갠 4개 파일 총합 1.1MB로 줄었습니다. 거의 절반입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 번들이 얼마나 무거운지는 감으로 알 수 없습니다. 여기서도 원칙은 똑같았습니다. 번들 애널라이저로 무엇이 용량을 차지하는지 눈으로 확인하고, 무거운 것부터 손댔습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 2: 이미지를 똑똑하게 불러오기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS 다음으로 무거운 짐은 이미지였습니다. 특히 랜딩 페이지처럼 이미지가 많은 화면에서는, 이미지를 어떤 순서로 불러오느냐가 LCP를 크게 좌우합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심 개념이 하나 있습니다. &lt;b&gt;첫 화면에 보이는 이미지와 보이지 않는 이미지를 다르게 취급해야 한다는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지를 열었을 때 사용자 눈에 가장 먼저, 가장 크게 들어오는 이미지. 보통 이게 LCP(Largest Contentful Paint)의 Lagest Content입니다. 이건 무조건 빨리 보여줘야 합니다. 반대로 가장 큰 이미지가 아닌 작은 이미지들은 굳이 빨리 보여줘야 할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한 가지 원칙을 세웠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 뷰포트에 나타나는 가장 큰 이미지 하나만 우선 로딩하고, 나머지는 전부 지연시킨다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 에서는 화면 밖 이미지들에는 지연 로딩을 걸어, 사용자가 스크롤로 가까이 갔을 때 비로소 불러오도록 처리하고 있습니다. 그러나 첫 화면에 보이는 이미지들은 우선 로딩 대상입니다. 그래서 첫 화면에 보이는 가장 큰 이미지가 아닌 이미지들을 의도적으로 지연시켰습니다. 덕분에 브라우저의 한정된 네트워크 대역폭이 &quot;지금 당장 중요한 이미지&quot;에 먼저 쓰이게 됐고, LCP를 당길 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 3: picture 태그 삽질담&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 편에서 가장 기억에 남는 삽질을 소개할 차례입니다. 분명 최적화를 하려고 한 일이 오히려 페이지를 느리게 만든, 그런 이야기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발단은 랜딩 페이지의 이미지였습니다. 모바일과 PC에서 서로 다른 이미지 파일을 쓰고 있었거든요. 모바일엔 세로로 긴 이미지, PC엔 가로로 넓은 이미지, 이런 식이었죠. 그래서 저는 &quot;지금 접속한 게 모바일인지 PC인지 판단해서, 거기 맞는 이미지를 내려주면 되겠다&quot;고 생각했습니다. 그리고 그 판단을 &lt;b&gt;Next.js 서버 컴포넌트에서&lt;/b&gt; 처리했습니다. 요청 헤더를 보고 모바일 여부 플래그를 계산해, 알맞은 이미지를 골라 렌더링하는 방식이었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리적으로는 깔끔해 보였습니다. 그런데 측정을 해보니 오히려 로드 속도가 떨어져 있었습니다. 한참을 들여다보고 나서야 원인을 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &quot;요청 헤더를 본다&quot;는 그 행위 자체였습니다. 서버 컴포넌트에서 매 요청의 헤더를 읽어 분기하는 순간, 이 페이지는 더 이상 &quot;모두에게 똑같은&quot; 페이지가 아니게 됩니다. Next.js 입장에서는 빌드 시점에 정적으로 미리 만들어 둘 수가 없는, 요청마다 매번 새로 그려야 하는 동적 페이지가 되어버린 겁니다. 미리 만들어 캐싱해 둘 수 있었던 페이지가, 요청이 올 때마다 서버에서 새로 렌더링되는 페이지로 강등된 셈이죠. 이미지 하나 똑똑하게 고르려다, 페이지 전체를 동적으로 만들어버린 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨달음은 단순했습니다. &quot;어떤 이미지를 보여줄지&quot;는 서버가 헤더를 보고 결정할 게 아니라, 브라우저가 알아서 고르게 두면 된다. 마침 이걸 위해 만들어진 표준이 있었습니다. HTML의 &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그입니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;!-- 뷰포트가 768px 이하면 모바일용 이미지 --&amp;gt;
  &amp;lt;source media=&quot;(max-width: 768px)&quot; srcset=&quot;/hero-mobile.webp&quot; /&amp;gt;
  &amp;lt;!-- 그 외에는 PC용 이미지 --&amp;gt;
  &amp;lt;img src=&quot;/hero-pc.webp&quot; alt=&quot;센디 메인 비주얼&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그는 여러 이미지 후보를 던져주면, 브라우저가 현재 뷰포트에 맞는 소스를 &lt;b&gt;알아서&lt;/b&gt; 골라 받아옵니다. 서버는 어떤 기기인지 알 필요도, 분기할 필요도 없습니다. 그저 모두에게 똑같은 HTML을 내려주면 되죠. 페이지는 다시 정적으로 캐싱될 수 있는 상태로 돌아왔고, 모바일&amp;middot;PC 이미지 분기도 그대로 유지됐습니다. 두 마리 토끼를 다 잡은 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 삽질에서 얻은 교훈은 지금도 종종 떠올립니다. 클라이언트가 알아서 처리할 수 있는 일을 굳이 서버로 끌고 오면, 종종 더 비싼 대가를 치른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 4: 인프라 레이어로 내려가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 애플리케이션 코드 안에서 할 수 있는 일이었습니다. 번들을 줄이고, 이미지를 똑똑하게 불러오고. 그런데 측정을 거듭하다 보니, 코드만으로는 더 이상 깎이지 않는 영역이 보이기 시작했습니다. 그래서 한 레이어 더 아래, &lt;b&gt;인프라&lt;/b&gt;로 내려갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 배포 구조는 단순했습니다. &lt;code&gt;next build&lt;/code&gt;로 빌드한 결과물을 그대로 Docker 컨테이너에 올려 Next.js 서버가 모든 요청을 처리하는 방식이었죠. 동작에는 문제가 없었습니다. 다만 한 가지 아쉬운 점이 있었습니다. 이미지, 폰트, JS&amp;middot;CSS 같은 &lt;b&gt;정적 파일까지 전부 Next.js 서버가 직접 응답&lt;/b&gt;하고 있었다는 것입니다. 자주 바뀌지도 않는 파일들을 매번 애플리케이션 서버가 처리하는 건, 잘하는 일을 엉뚱한 사람에게 시키는 셈이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음과 같이 구조를 바꿨습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Next.js standalone 모드&lt;/b&gt;: &lt;code&gt;output: 'standalone'&lt;/code&gt; 옵션을 켜면, 실행에 꼭 필요한 파일만 추려 가벼운 산출물을 만들어 줍니다. 컨테이너 이미지가 한결 가벼워졌죠.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;앞단에 nginx를 세우기&lt;/b&gt;: 정적 파일 요청은 nginx가 직접 받아 캐싱해서 빠르게 응답하고, 실제 렌더링이 필요한 요청만 뒤의 Next.js 서버로 넘기도록 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;# 정적 파일은 nginx가 직접 응답하고 길게 캐싱
location /_next/static/ {
  proxy_pass http://nextjs_upstream;
  proxy_cache_valid 200 1y;
  add_header Cache-Control &quot;public, max-age=31536000, immutable&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;역할을 나눈 것&lt;/b&gt;이었습니다. 정적 파일은 그 일을 잘하는 nginx에게, 동적 렌더링은 Next.js에게. 각자 잘하는 일에 집중시키니, 정적 자원이 캐시에서 빠르게 날아가면서 전체 응답이 가벼워졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아보면 성능 개선에서 한 일은 레이어만 달랐지 본질은 하나였습니다. 측정해서 범인을 찾고, 하나 고치고, 다시 측정한다. 번들이든 이미지든 인프라든, 이 지루한 루프를 레이어를 바꿔가며 반복했을 뿐입니다. 화려한 한 방은 없었습니다. 그리고 picture 태그를 작업하며 배웠듯, 좋은 의도가 늘 좋은 결과로 이어지는 것도 아니었죠. 그래서 더더욱, 감이 아니라 숫자로 확인하는 습관이 중요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 당겨진 FCP&amp;middot;LCP는 사용자 경험을 좋게 했을 뿐 아니라, Google이 보는 Core Web Vitals 신호로서 SEO의 토대가 되어주었습니다. 하지만 페이지가 빨라졌다고 검색엔진이 알아서 우리 사이트를 잘 이해해 주는 건 아니었습니다. 빠른 페이지를 &quot;검색엔진이 제대로 읽고, 예쁘게 노출하고, 클릭하고 싶게&quot; 만드는 건 또 다른 작업이었거든요. 그 이야기는 다음 편, 테크니컬 SEO에서 이어가겠습니다.&lt;/p&gt;</description>
      <category>커리어/Sendy</category>
      <category>FCP</category>
      <category>geo</category>
      <category>lcp</category>
      <category>sendy</category>
      <category>SEO</category>
      <category>센디</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/654</guid>
      <comments>https://insengnewbie.tistory.com/654#entry654comment</comments>
      <pubDate>Sun, 28 Jun 2026 02:50:24 +0900</pubDate>
    </item>
    <item>
      <title>센디 SEO 3배 개선기(1) - 어쩌다 SEO를 맡게 되었나</title>
      <link>https://insengnewbie.tistory.com/653</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;어느 날, CTO님이 말했습니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;요즘 페이지가 좀 느린 것 같지 않아요?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬랙으로 날아온 이 한 문장이 시작이었습니다. 그땐 이게 9개월 뒤 트래픽을 3배로 만드는 여정의 입구일 줄은 몰랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 센디의 프론트엔드 개발자이자, 자칭(自稱) 사내 SEO 엔지니어이기도 합니다. &quot;자칭&quot;이라고 적은 데는 이유가 있습니다. 누가 시켜서 맡은 직함이 아니라, 어쩌다 보니 이 일에 가장 깊게 발을 담그게 된 사람이 저였을 뿐이기 때문입니다. 이 글은 그 &quot;어쩌다&quot;가 시작됐는지에 대해서부터 &amp;ldquo;어떻게&amp;rdquo; 문제를 해결했는지에 대한 기록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;센디는 화물과 차주를 매칭하는 물류 플랫폼입니다. 화주가 보낼 짐을 올리면 적합한 차주에게 연결해 주는, 말하자면 화물 운송계의 중개 서비스라고 보시면 됩니다. 그 말은 곧, 우리 서비스를 처음 접하는 사람들은 특정 키워드에 대한 검색을 통해 들어오는 경우가 많다는 뜻이기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아가서 &quot;페이지가 느린 것 같다&quot;는 피드백을 받았을 때, 제가 할 일은 명확했습니다. 정말 느린지, 느리다면 어디가 느린지를 숫자로 확인하는 것이었죠. 막연히 &quot;체감상 느리다&quot;는 느낌만으로는 무엇도 고칠 수 없으니까요. 그래서 가장 먼저 Chrome DevTools의 Performance 탭을 열었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능을 파다가 SEO에 빠지다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Performance 탭을 켜고 페이지를 분석하다 보니, 자연스럽게 &quot;웹 성능&quot;이라는 키워드로 이런저런 아티클을 찾아 읽게 됐습니다. LCP가 어떻고 FCP가 어떻고 하는 지표들을 공부하던 중이었죠. 그런데 이상하게도, 성능을 다루는 글들은 약속이나 한 듯 한 곳으로 흘러가고 있었습니다. 바로 &lt;b&gt;검색엔진최적화(SEO)&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해 보면 당연한 일이었습니다. 페이지가 빠르다는 건 단순히 사용자가 쾌적하게 쓴다는 의미를 넘어, 검색엔진이 좋아하는 신호이기도 하니까요. Google은 페이지 경험(Core Web Vitals)을 랭킹 요소로 본다고 공공연히 밝히고 있었고, 제가 고치고 있던 LCP&amp;middot;FCP가 바로 그 지표의 일부였습니다. 성능 개선을 하다 보니 어느새 SEO의 입구에 서 있었던 셈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심이 생기니 더 깊이 파게 됐습니다. 그리고 파면 팔수록, 센디처럼 &quot;검색으로 처음 유입되는 사용자&quot;가 많은 서비스에게 SEO가 얼마나 중요한지가 또렷해졌습니다. 앞서 흘려 둔 복선이 여기서 회수되는 순간이었죠. 우리 서비스는 SEO를 잘할수록 득을 볼 구조였는데, 그동안 아무도 본격적으로 챙기지 않고 있었던 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회사의 니즈와 맞아떨어지다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 좋은 타이밍이었습니다. 제가 SEO에 빠져들 무렵, CTO님도 비슷한 고민을 하고 있었거든요. 그동안 신규 유입의 상당 부분을 유료 웹 마케팅에 의존하고 있었는데, 여기엔 두 가지 문제가 있었습니다. 하나는 비용이고, 다른 하나는 지속성입니다. 광고는 돈을 쓰는 동안만 사람이 들어옵니다. 돈을 멈추면 유입도 멈추죠. 반면 SEO로 쌓은 오가닉 트래픽은 한번 자리를 잡으면 광고비 없이도 꾸준히 들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 회사의 방향은 자연스럽게 &quot;유료 마케팅을 줄이고 SEO에 집중하자&quot;로 모였습니다. 비용은 절감하면서 오가닉 트래픽은 늘리겠다는, 두 마리 토끼를 노리는 전략이었죠. 제가 개인적으로 품고 있던 관심과 회사의 니즈가 정확히 맞아떨어진 순간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때부터 일이 커졌습니다. 저 혼자 짬짬이 만지던 수준을 넘어, PM&amp;middot;프론트엔드&amp;middot;백엔드&amp;middot;디자이너가 한데 모인 &lt;b&gt;SEO TF&lt;/b&gt;가 꾸려졌거든요. TF의 첫 미션은 검색엔진이 색인할 콘텐츠를 본격적으로 늘리는 것이었습니다. 블로그, 고객센터, 서비스 소개처럼 검색에 걸릴 만한 페이지들을 차근차근 채워나가기 시작했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 글에서 콘텐츠 이야기는 잠시 접어두려 합니다. 제가 가장 깊게 파고든 영역은 따로 있었거든요. 바로 콘텐츠와는 별개로 묵묵히 굴러가던 &lt;b&gt;테크니컬 SEO&lt;/b&gt;, 그리고 그 출발점이었던 &lt;b&gt;성능 개선&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9개월 뒤, 그리고 이 시리즈&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buyvxW/dJMcaf78sTa/S0VtbL9dkjQDQiwlH4pnq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buyvxW/dJMcaf78sTa/S0VtbL9dkjQDQiwlH4pnq0/img.png&quot; data-alt=&quot;구글 서치 콘솔&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buyvxW/dJMcaf78sTa/S0VtbL9dkjQDQiwlH4pnq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuyvxW%2FdJMcaf78sTa%2FS0VtbL9dkjQDQiwlH4pnq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;463&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글 서치 콘솔&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서두에서 흘렸던 &quot;9개월 뒤 트래픽 3배&quot;라는 이야기를 기억하실 겁니다. 구체적인 숫자를 잠깐 꺼내 보면, 유료 마케팅을 종료한 2025년 10월에 월 노출 53,342회&amp;middot;클릭 1,071회였던 것이, 2026년 5월에는 노출 144,666회&amp;middot;클릭 3,355회가 됐습니다. 광고를 줄였는데 유입은 3배가 된 셈이죠. 물론 이게 전부 제 손끝에서 나온 성과는 아닙니다. TF 전체가, 특히 콘텐츠를 채워준 동료들이 함께 만든 결과니까요. 다만 그중 성능과 테크니컬 SEO라는 조각은 분명 제가 책임지고 굴린 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈는 그 조각들에 대한 기록입니다. 앞으로 세 편에 걸쳐 이렇게 풀어보려 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;2편 - 성능/인프라&lt;/b&gt;: LCP&amp;middot;FCP를 잡기 위해 DevTools로 끈질기게 모니터링하고, 번들을 줄이고, 이미지 로딩을 손보고, 끝내 인프라까지 갈아엎은 이야기.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3편 - 테크니컬 SEO&lt;/b&gt;: 사이트맵을&amp;nbsp;쪼개&amp;nbsp;크롤링&amp;nbsp;버짓을&amp;nbsp;절감하고,&amp;nbsp;캐노니컬&amp;nbsp;URL과&amp;nbsp;인덱싱&amp;nbsp;점검,&amp;nbsp;구조화된&amp;nbsp;데이터로&amp;nbsp;검색&amp;nbsp;결과를&amp;nbsp;예쁘게&amp;nbsp;만들어낸&amp;nbsp;기록.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;4편 - 결론&lt;/b&gt;: 위 숫자들을 Search Console로 어떻게 들여다봤는지, 무엇이 통했고 무엇은 끝내 아쉬웠는지에 대한 회고.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 SEO 전문가가 아니라, 어쩌다 이 일을 떠맡게 된 한 프론트엔드 개발자의 시행착오 기록입니다. 저처럼 &quot;우리 서비스도 SEO 좀 해야 하는데&amp;hellip;&quot; 하고 막막해하는 분께 작은 가이드가 됐으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;센디가 어떤 서비스인지 궁금하시다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://sendy.ai/blog/%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EB%B0%98%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot;&gt;포장이사 vs 반포장이사 vs 용달이사 | 블로그&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782583073295&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;포장이사 vs 반포장이사 vs 용달이사 뭐가 달라요? | 센디 블로그&quot; data-og-description=&quot;이사를 앞두고 포장이사&amp;middot;반포장이사&amp;middot;용달이사 중 뭘 골라야 할지 모르겠다면, 이 글 하나로 해결해요. 비용&amp;middot;범위&amp;middot;상황별 선택 기준을 한 번에 비교했어요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/blog/%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EB%B0%98%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; data-og-url=&quot;https://sendy.ai/blog/%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EB%B0%98%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cql5fz/dJMb9hDbvbN/nuoFmJjBuVgofLiJzkKd60/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941,https://scrap.kakaocdn.net/dn/dLMRmb/dJMb9g5llo0/hStbO9UlMqwq0EeKI9Ayuk/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941,https://scrap.kakaocdn.net/dn/miUJk/dJMb9efomXE/RcVeMway0YrmSRYIddZJOK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EB%B0%98%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/blog/%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EB%B0%98%ED%8F%AC%EC%9E%A5%EC%9D%B4%EC%82%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cql5fz/dJMb9hDbvbN/nuoFmJjBuVgofLiJzkKd60/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941,https://scrap.kakaocdn.net/dn/dLMRmb/dJMb9g5llo0/hStbO9UlMqwq0EeKI9Ayuk/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941,https://scrap.kakaocdn.net/dn/miUJk/dJMb9efomXE/RcVeMway0YrmSRYIddZJOK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;포장이사 vs 반포장이사 vs 용달이사 뭐가 달라요? | 센디 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이사를 앞두고 포장이사&amp;middot;반포장이사&amp;middot;용달이사 중 뭘 골라야 할지 모르겠다면, 이 글 하나로 해결해요. 비용&amp;middot;범위&amp;middot;상황별 선택 기준을 한 번에 비교했어요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a title=&quot;센디가 무엇인가요? | 고객센터&quot; href=&quot;https://sendy.ai/%EA%B3%A0%EA%B0%9D%EC%84%BC%ED%84%B0/%EA%B8%B0%ED%83%80/%EC%84%BC%EB%94%94%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;센디가 무엇인가요? | 고객센터&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782452051676&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;센디가 무엇인가요? | 기타 가이드 | 센디 고객센터&quot; data-og-description=&quot;센디는 용달&amp;middot;화물 운송이 필요한 고객과 기사님을 연결해 주는 화물 운송 플랫폼 서비스입니다.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/%EA%B3%A0%EA%B0%9D%EC%84%BC%ED%84%B0/%EA%B8%B0%ED%83%80/%EC%84%BC%EB%94%94%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; data-og-url=&quot;https://sendy.ai/%EA%B3%A0%EA%B0%9D%EC%84%BC%ED%84%B0/%EA%B8%B0%ED%83%80/%EC%84%BC%EB%94%94%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RXM8T/dJMb9c9H1MZ/ukKef6bNNgB6IdVkE0kAV0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/TKBOi/dJMb9c9H1MY/o0E0wb2lu6Hbs05lucJycK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bluMqR/dJMb8WMzWb7/V1J0LDH7at1dPHO5aZhLgK/img.png?width=1440&amp;amp;height=720&amp;amp;face=0_0_1440_720&quot;&gt;&lt;a href=&quot;https://sendy.ai/%EA%B3%A0%EA%B0%9D%EC%84%BC%ED%84%B0/%EA%B8%B0%ED%83%80/%EC%84%BC%EB%94%94%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/%EA%B3%A0%EA%B0%9D%EC%84%BC%ED%84%B0/%EA%B8%B0%ED%83%80/%EC%84%BC%EB%94%94%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RXM8T/dJMb9c9H1MZ/ukKef6bNNgB6IdVkE0kAV0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/TKBOi/dJMb9c9H1MY/o0E0wb2lu6Hbs05lucJycK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bluMqR/dJMb8WMzWb7/V1J0LDH7at1dPHO5aZhLgK/img.png?width=1440&amp;amp;height=720&amp;amp;face=0_0_1440_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디가 무엇인가요? | 기타 가이드 | 센디 고객센터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;센디는 용달&amp;middot;화물 운송이 필요한 고객과 기사님을 연결해 주는 화물 운송 플랫폼 서비스입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>커리어/Sendy</category>
      <category>SEO</category>
      <category>센디</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/653</guid>
      <comments>https://insengnewbie.tistory.com/653#entry653comment</comments>
      <pubDate>Tue, 23 Jun 2026 02:19:40 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 가족이라는 사치</title>
      <link>https://insengnewbie.tistory.com/652</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBvciX/dJMcajvS7gj/iDc6x26b4eD8S1cJJdqZJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBvciX/dJMcajvS7gj/iDc6x26b4eD8S1cJJdqZJK/img.png&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBvciX/dJMcajvS7gj/iDc6x26b4eD8S1cJJdqZJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBvciX%2FdJMcajvS7gj%2FiDc6x26b4eD8S1cJJdqZJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Og5oq/dJMcaglBm68/KEDUrKQ9jrvMnmeDkXHWr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Og5oq/dJMcaglBm68/KEDUrKQ9jrvMnmeDkXHWr0/img.png&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Og5oq/dJMcaglBm68/KEDUrKQ9jrvMnmeDkXHWr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOg5oq%2FdJMcaglBm68%2FKEDUrKQ9jrvMnmeDkXHWr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시: 2026-06-14 ~ 2026-06-19&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목: 가족이라는 사치 - 진미정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평점: 5/5점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;가족이라는 문장에 끌리다&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 채널 &amp;ldquo;이동진의 파이아키아&amp;rdquo;에서 추천받아 읽게 되었다. &amp;ldquo;모든 것이 불확실하고 불안정해진 세상. 가족마저 변하고 있다.&amp;rdquo;라는 뒷표지 문장에 마음이 끌렸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;변화하는 가족의 형태&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『가족이라는 사치』에서 저자는 연애, 결혼, 출산, 양육, 돌봄 등 다양한 영역에서 어떤 변화가 생겼고, 왜 그런 변화가 생겼는지 다각적인 측면에서 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도 사람들이 실수하기 쉬운 통계의 오해를 다룬 부분이 인상 깊었다. 해당 챕터에서 &amp;ldquo;가구&amp;rdquo;와 &amp;ldquo;가족&amp;rdquo;이 서로 다른 단위라는 것을 배웠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;불확실성의 시대와 안전한 선택&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자는 젊은 세대에서 &amp;ldquo;나는 솔로&amp;rdquo; 같은 일반인 연애 프로그램이 흥행하는 이유를 분석하기도 한다. &amp;ldquo;연애는 원래 불확실성이 가득한 일인데, 불안정한 경제 상황까지 더해져 불확실성이 증폭된다.&amp;rdquo; 우리는 불확실한 상황에서는 안전하고 편안한 선택을 하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 짧은 식견으로 볼 때, 현대 사회의 불확실성은 과거와 비교할 수 없을 만큼 증폭된 것 같다. 현대 사회는 스트레스가 만연한 사회다. 스트레스란 자신의 내부에서 형성되는 것이 아니라, 외부의 마찰이나 자극으로 들어오는 요소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 기술 혁신으로 더 많은 정보, 더 많은 사회적 관계, 더 많은 연결을 얻었지만, 역설적으로 스트레스도 많아졌다. 우리는 정보가 많으면 무조건 좋다고 생각하는 경향이 있지만, 실상 우리의 생물학적 뇌는 이 정도의 정보량을 처리할 수 있을 만큼 진화하지 못했다. 그렇기에 금방 예민해지고 피로해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 가족이 사치재가 된 것이 아닐까. 우리 삶의 가치를 잘 인식하는 것이 중요한 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;공동 육아가 어려워진 사회&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자는 또한 사회 구조적인 부분에 대해서도 꼬집는다. 옛날 호모 사피엔스는 가족과 이웃을 넘어 마을 단위로 공동 육아를 하며 아이를 키우는 종족이었다. 그러나 현대는 공동 육아가 불가능해진 시대다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇이 우리 사이의 경계심을 키우고 신뢰도를 떨어뜨렸을까? 이유는 여러 가지가 있겠지만, 저자는 사회 구조적으로 심리적 안정감이 뒷받침되어야 한다고 말한다. 나도 여기에 동감했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKQUHd/dJMcafAgAA3/ROqLmCyBd4gikwYzBf3YlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKQUHd/dJMcafAgAA3/ROqLmCyBd4gikwYzBf3YlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKQUHd/dJMcafAgAA3/ROqLmCyBd4gikwYzBf3YlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKQUHd%2FdJMcafAgAA3%2FROqLmCyBd4gikwYzBf3YlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;289&quot; height=&quot;513&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 인생책 중 하나인 &quot;집단 착각(토드 로즈)&quot;도 책에 언급된다. &quot;나는 경쟁적 양육(영어유치원 같은거)이 옳다고 생각하지 않지만, 다른 사람들은 그렇게 할 것이기 때문에 조기 사교육이 중요해진다. 집단의 다수가 믿는다고 생각하지만, 실제로는 그렇지 않은 것이 &lt;b&gt;집단 착각&lt;/b&gt;이다&quot;.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;그래도 희망은 있다&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 마지막 장을 읽을 때쯤에는 &amp;ldquo;가족&amp;rdquo;을 사치재로 만든 이유가 너무나 많아서 절망적이기까지 했다. 그러나 저자는 희망이 있다고 말한다. 우리 삶의 가치를 의식적으로 인식하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 간혹 너무 바쁘게 달리느라 무엇을 위해 달리고 있는지 잊을 때가 있다. 그럴 때마다 우리가 무엇을 위해 열심히 달리는지 돌아볼 필요가 있을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;이런 사람에게 추천하고 싶은 책&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;가족&amp;rdquo;의 가치에 대해 궁금한 사람들에게 추천하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/p0RSM2ZDXos?si=TbdIXMM8eFl-W1jx&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/p0RSM2ZDXos?si=TbdIXMM8eFl-W1jx&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=p0RSM2ZDXos&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/nf5uI/dJMb9frOBUK/PpR9pai6kmYVfjZkUODelK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=320_124_1166_496,https://scrap.kakaocdn.net/dn/czTw3E/dJMb9gxuFtZ/DcnVEKIVVVIdUS0ip7nGTK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=320_124_1166_496&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;관계의 &amp;lsquo;가성비&amp;rsquo;를 따지는 세상, 이 시대의 가족은? [4월의 베스트북]&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/p0RSM2ZDXos&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>가족이라는사치</category>
      <category>서평</category>
      <category>이동진</category>
      <category>집단착각</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/652</guid>
      <comments>https://insengnewbie.tistory.com/652#entry652comment</comments>
      <pubDate>Fri, 19 Jun 2026 13:30:05 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 나의 동기에 대하여</title>
      <link>https://insengnewbie.tistory.com/651</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 동기부여가 잘 되지 않는다. 스스로 정체되어 있음을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 자연스러운 회복기라고 생각했다. 지난 1년 동안 꽤 빡세게 일했고, 많은 기여를 했고, 나름대로 계속 달려왔다. 그러니 이제는 잠깐 여유를 가져도 괜찮다고 생각했다. 연애도 시작했으니 일만이 아니라 삶에도 조금 더 집중해보자고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 그 시간이 생각보다 길어진 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 이런 질문이 들었다. 나는 왜 정체되어 있을까? 애초에 정말 정체된 것이 맞을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재미있는 것은, 막연하던 감정을 &amp;ldquo;정체감&amp;rdquo;이라는 말로 언어화하고 나니 오히려 내가 &lt;b&gt;정체되어 있고 싶지 않다&lt;/b&gt;는 사실이 선명해졌다는 점이다. 적어도 내 안에는 여전히 성장하고 싶다는 욕구가 남아 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 나는 이 정체감에서 어떻게 벗어날 수 있을까. 앞으로는 어떤 방식으로 성장해야 할까.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;나는 정말 정체되어 있는가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전의 나는 삶의 많은 시간을 개발에 투자했다. 더 많이 기여하고 싶었고, 더 많이 인정받고 싶었다. 자연스럽게 업무량과 생산성을 늘리는 방식으로 성장해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 최근 1년 동안은 개발 외적인 삶의 비중이 커졌다. 연애를 시작했고, 책을 읽었고, 복싱을 했고, 게임도 했다. 일 외적으로 집중할 수 있는 것들이 생기면서 삶은 더 건강해졌고, 더 지속가능해졌다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 변화 자체를 후회하지는 않는다. 오히려 필요했던 변화라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 개발자로서의 실력은 확실히 정체된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 링크드인, 카카오톡 오픈채팅방, 블로그 글쓰기 모임 등에 참여하며 서울&amp;middot;경기권 개발자들과 접점을 만들려고 했다. 우물 안 개구리에 머무르지 않기 위한 나름의 노력이었다. 하지만 요즘은 그런 외부 활동을 거의 하지 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무에서도 답답함이 있다. 내가 개발에 참여한 기능들이 실제 지표 상승으로 이어지고 있다는 확신이 없다. 입사 초반에는 우리가 만들고 있는 것들이 분명히 지표를 끌어올릴 것이라고 믿었다. 그래서 이것저것 시도했고, 더 잘 만들기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 2년이 지나도 기대했던 만큼의 변화가 보이지 않았고, 믿고 의지하던 팀원들이 조금씩 회사를 떠나는 모습도 보았다. 그 과정에서 어쩌면 나는 회사에서 더 노력해야 할 이유를 조금씩 잃어버린 것인지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회복기가 길어지면서 관성이 생긴 것 같기도 하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;내가 말하는 &amp;ldquo;실력&amp;rdquo;은 무엇인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 다시 질문이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 말하는 &amp;ldquo;실력&amp;rdquo;이란 정확히 무엇일까? 나는 지금 무엇이 답답해서 이런 감정을 느끼는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 나는 주로 작업량과 생산성을 늘리는 방식으로 성장해왔다. 더 빠르게 만들고, 더 많이 처리하고, 더 안정적으로 구현하는 사람이 되는 것이 중요한 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제는 그다음 단계의 성장이 필요한 시점인지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 단순히 구현 속도나 작업량만이 아니라, 문제를 정의하는 능력, 설계하는 능력, 제품에 영향을 미치는 능력, 협업 방식을 개선하는 능력, 제품 감각, 리더십 같은 것들을 길러야 하는 게 아닐까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 질문은 여기로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 앞으로 어떤 엔지니어가 되고 싶은가.&lt;br /&gt;나는 지금 무엇을 하고 싶은가.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;목표를 다시 설정해야 할 때&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 나에게 필요한 것은 목표의 재설정인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 1년 동안 나는 어떤 엔지니어로 보이고 싶은가. 무엇에 집중해야 나 자신에게도, 팀에게도 의미 있는 성장을 만들 수 있을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최전선까지는 아니더라도 AI 트렌드를 적절히 읽고 팀에 잘 도입하고 싶다. 센디 개발자들이 어디 가서 &amp;ldquo;AI를 꽤 잘 쓰는 팀&amp;rdquo;이라는 소리를 들을 수 있도록, 업무 방식과 개발 생산성을 개선하는 데 기여하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO와 GEO 영역에서도 뒤처지지 않도록 꾸준히 관리하고 싶다. 앞으로 사용자가 서비스를 발견하는 방식은 계속 변할 것이고, 그 변화에 엔지니어링 관점에서 대응할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 나는 더 이상 단순히 &amp;ldquo;구현을 빠르게 많이 하는 사람&amp;rdquo;으로만 남고 싶지 않다. 코드 품질, 성능, DX, 아키텍처 기준을 끌어올리는 사람이 되고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성장하고 싶다.&lt;br /&gt;나를 증명하고 싶다.&lt;br /&gt;여기서 멈추고 싶지 않다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모든 것을 되돌릴 필요는 없다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 해서 지금의 삶의 밸런스를 예전으로 되돌리고 싶지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 모든 시간을 쏟아붓던 시절로 돌아가고 싶은 것은 아니다. 지금의 나는 일 외적인 삶도 중요하다는 것을 알고 있고, 그것이 나를 더 오래 지속가능하게 만든다는 것도 안다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 지금 필요한 것은 &lt;b&gt;모든 것을 다시 손에 쥐려는 무모함&lt;/b&gt;이 아니다. 필요한 것은 &lt;b&gt;우선순위&lt;/b&gt; 배정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전처럼 무작정 더 많이 하는 방식이 아니라, 내가 정말 성장하고 싶은 방향을 정하고 그 방향으로 꾸준히 시간을 쓰는 것. 그게 지금 나에게 필요한 방식인 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;작게 다시 시작하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거창한 결심보다 중요한 것은 작게 다시 시작하는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출근 후 스탠드업 미팅을 제외한 30분 동안은 구독해둔 신규 기술 아티클을 읽어보려고 한다. 밀린 아티클도 하나씩 처리해보려 한다. 퇴근 10분 전에는 데일리 노트를 정리하면서 오늘 무엇을 배웠고, 무엇을 놓쳤고, 내일은 무엇에 집중할지 기록해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크드인 알고리즘도 다시 정리하고 싶다. 어느 순간 SEO 관련 콘텐츠 위주로 점철되어버린 피드를 조금씩 되돌려, 다시 프론트엔드와 제품 개발에 관한 자극을 받을 수 있는 환경을 만들고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울&amp;middot;경기권 프론트엔드 개발자들은 요즘 어떤 고민을 하고 있는지도 다시 알고 싶다. 친구에게 물어보는 것부터 시작해볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 FECONF 2026 발표도 준비해보고 싶다. 발표를 목표로 삼는 것은 단순히 무대에 서기 위해서가 아니다. 내가 어떤 문제를 깊게 고민했고, 어떤 방식으로 해결했으며, 그것을 다른 사람에게 설명할 수 있을 만큼 정리했다는 증거가 될 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정체감은 불편한 감정이지만, 동시에 방향을 다시 묻는 신호일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 지금 멈춰 있고 싶지 않다.&lt;br /&gt;그러니 다시 움직여보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전과 같은 방식은 아니어도 괜찮다.&lt;br /&gt;이제는 지금의 나에게 맞는 방식으로 성장하면 된다.&lt;/p&gt;</description>
      <category>커리어</category>
      <category>동기</category>
      <category>정체감</category>
      <category>회고</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/651</guid>
      <comments>https://insengnewbie.tistory.com/651#entry651comment</comments>
      <pubDate>Mon, 15 Jun 2026 13:02:41 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 박곰희 연금부자수업</title>
      <link>https://insengnewbie.tistory.com/650</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q4iNE/dJMcaglv0f0/3BX4ba4WgJoYb7k0V2T5RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q4iNE/dJMcaglv0f0/3BX4ba4WgJoYb7k0V2T5RK/img.png&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q4iNE/dJMcaglv0f0/3BX4ba4WgJoYb7k0V2T5RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ4iNE%2FdJMcaglv0f0%2F3BX4ba4WgJoYb7k0V2T5RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7AKZ/dJMcaglv0f7/aVPscOKCVMT2ZQAlIo0TKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7AKZ/dJMcaglv0f7/aVPscOKCVMT2ZQAlIo0TKk/img.png&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7AKZ/dJMcaglv0f7/aVPscOKCVMT2ZQAlIo0TKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7AKZ%2FdJMcaglv0f7%2FaVPscOKCVMT2ZQAlIo0TKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노후준비가 뭔지도 모르겠고 막막하기만 한 사람들에게 추천하고 싶은 책.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년부터 박곰희 유튜브 채널을 봐왔고, 25년에 출간됐을 때 눈여겨봤지만, 이제서야 읽게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 책의 내용은 전부 박곰희 채널에 있던 내용이라 나에게 새로울 건 없었다. 까먹었던 것들을 조금 복습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 추천하고 싶은 이유는 코 앞의 이득만을 바라보는 우리에게 미래의 우리 자신을 위해 씨앗을 심으라고 강조하기 때문이다. 요즘 같은 불장 시기에 누군가 반도체주로 얼마를 벌었다 같은 소리를 들으면 우리도 모르게 마음이 조급해지고만다. 누가 삼전을 샀다네 하닉를 샀다네 주워듣고는 축제 속 인파에 휩쓸리듯 끌려가고 만다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 나는 나 혼자만 5배/10배를 먹을 수 있다는 기적을 꿈꾸지 않는다. 저점에 매수해서 고점에 팔기에는 나는 너무 무지하다. 똑똑하다고 주식을 잘 하는 것도 아니고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 스스로를 과신하지말고 전략을 따라야 할 필요가 있다고 책에서 말한다. &amp;ldquo;박곰희 연금부자수업&amp;rdquo;은 우리가 왜 노후준비를 해야하는지, 연금계좌가 왜 필수적인지, 스스로 챙기 않으면 백세시대에 우리의 미래는 불확실하기 그지없다고 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽고 나면 크게 특별할게 없는 방법이라는 생각이 든다. 그래서 이 책을 추천하고 싶다. 우리는 누구라도 노후를 안정적으로 꾸릴 수 있으며 그럴 권리가 있다고 믿기에 이 책을 읽었으면 한다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>독서</category>
      <category>박곰희</category>
      <category>박곰희연금부자수업</category>
      <category>서평</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/650</guid>
      <comments>https://insengnewbie.tistory.com/650#entry650comment</comments>
      <pubDate>Fri, 12 Jun 2026 10:12:26 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 생각 중독 - 생각이 너무 많은 사람에게</title>
      <link>https://insengnewbie.tistory.com/649</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000212601766&quot;&gt;https://product.kyobobook.co.kr/detail/S000212601766&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생각 중독：불안과 후회를 끊어내고 오늘을 사는 법&lt;/b&gt;&lt;br /&gt;닉 트렌턴 저 / 박지선 역 | 2024.02.29 | 4.5/5점&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 책을 집어 든 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 말하면, &quot;생각 중독&quot;이라는 제목이 먼저 눈에 들어왔다. 요즘 들어 머릿속이 너무 시끄럽다는 느낌이 자주 들었다. 정확히는 하나의 아이디어를 떠올리면 거기서 또 다른 아이디어가 파생되고, 그게 다시 열 개로 불어나고, 결국 손대야 할 일들이 산처럼 쌓이지만 아무것도 제대로 시작하지 못하는 상태.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이걸 &quot;불안&quot;이라고 생각한 적은 없었다. 나는 잡생각이 많은 사람이 아니라, 아이디어가 넘치는 사람이라고 스스로를 정의해왔으니까. 그런데 이 책을 다 읽고 나서 그 정의를 다시 돌아보게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 전제: 생각 과잉은 불안의 다른 이름이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 1장에서 저자는 꽤 도발적인 주장을 꺼낸다. 생각 과잉은 단순히 &quot;생각이 많은 것&quot;이 아니라, &lt;b&gt;불안이 표현되는 방식&lt;/b&gt;이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각하는 능력은 경이로운 선물이다. 다만, 매우 유용한 도구인 우리 뇌는 생각을 '너무 많이' 하면 오히려 능력이 떨어진다는 것이 문제다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뇌과학자 마커스 레이츨의 말도 인상적이었다. &quot;뇌는 행복이 아니라 생존하기 위해서 진화했다.&quot; 수렵 채집 시절의 뇌를 그대로 장착한 채, 현대의 폭발적인 정보량과 외부 자극 속에서 살아가고 있으니, 우리가 만성적으로 과부하 상태인 건 어찌 보면 당연한 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장을 읽을 때까지만 해도 &quot;이건 내 얘기가 아닌데?&quot;라고 생각했다. 나는 불안해서 생각이 많은 게 아니라 그냥 아이디어가 많은 거라고. 그런데 책을 넘길수록, 그 구분이 생각만큼 명확하지 않다는 걸 깨달았다. 정신없이 분기하는 아이디어들, 멈추지 못하는 머릿속. 그게 불안의 얼굴이 아니라고 누가 단언할 수 있을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결책은 인식에서 시작한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 2장에서 저자는 생각이 너무 많은 사람에게 일반적인 스트레스 해소법(산책, 명상 등)을 권하는 건 대부분 소용없다고 잘라 말한다. 왜냐하면 생각 과잉자 대부분은 &lt;b&gt;무엇이 문제인지조차 인식하지 못하고&lt;/b&gt; 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자가 제시하는 핵심은 간단하다. 자신을 &lt;b&gt;인식&lt;/b&gt;하는 것. 상황을 판단하거나 집착하거나 저항하지 않으면서, 내면의 상태에 주의를 기울이는 것. 불안과 인식의 차이도 짚어주는데, 불안에는 감정이 담겨 있지만 인식은 그 감정과 거리를 두는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 기법으로는 &lt;b&gt;4A 스트레스 관리법&lt;/b&gt;(회피&amp;middot;변경&amp;middot;수용&amp;middot;적응), &lt;b&gt;5-4-3-2-1 그라운딩 기법&lt;/b&gt;(현재의 다섯 가지 감각에 집중), &lt;b&gt;스트레스 일기&lt;/b&gt; 등을 소개한다. 딱히 새로운 기법들은 아니지만, &quot;왜 이 기법이 생각 과잉에 효과적인지&quot;를 먼저 설명하고 들어오는 방식이 설득력 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생각 과잉의 예상치 못한 원인: 시간 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3장에서 가장 의외였던 내용은 &lt;b&gt;잘못된 시간 관리&lt;/b&gt;가 불안의 주요 원인이라는 지적이다. 우리는 대개 자신을 비참하게 만드는 일을 우선시하고, 진정으로 삶을 풍요롭게 만드는 일에는 시간을 충분히 쏟지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앨런식 입력값 처리법&lt;/b&gt;이 특히 와닿았다. 전화, 이메일, 소소한 외부 자극에 자신이 어떻게 반응하는지를 분석하고, 어떤 자극에 먼저 대응할지 미리 계획해두는 것이다. 나는 사소한 내부&amp;middot;외부 자극에 반응하여 관심이 금세 돌려지는 편이다. 머릿속에서 불현듯 떠오르는 아이디어든, 까먹고 있던 심부름이 생각났든, 흥미로운 아티클을 읽다 새로운 아이디어가 떠올랐든. 이 모든 것들이 주의를 분산시키는 &quot;입력값&quot;이라는 프레임으로 이해하니 훨씬 다루기 쉬워 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생각을 바꾸려면 몸도 함께&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4장은 극심한 스트레스 상황에서 쓸 수 있는 기법들을 다룬다. 자율 이완 훈련, 유도 심상, 점진적 근육 이완, 걱정 미루기. 이론보다는 실용 중심의 챕터인데, &quot;삶의 모든 것을 계획할 수는 없다&quot;는 전제에서 시작한다는 점이 솔직하게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 실제로 써먹어본 건 &lt;b&gt;걱정 미루기&lt;/b&gt;다. 살을 빼야 한다는 생각이 머릿속을 맴돌고 있었는데, 지금 당장 어떻게 할 수 있는 상황이 아니었다. 예전이라면 그 생각을 붙잡고 어떻게든 해결책을 찾으려 했을 것이다. 그 대신 &quot;이 걱정은 지금 생각해봐야 소용없다. 나중에 생각하자&quot;고 의식적으로 미뤘다. 효과가 있었다. 머릿속에서 그 생각이 사라진 건 아니었지만, 지금 당장 그 생각이 나를 잡아먹지는 않게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5장에서는 책에서 가장 인상적인 개념이 등장한다. &lt;b&gt;인지 왜곡&lt;/b&gt;이다. 생각 과잉자들의 핵심 문제는 자신의 생각을 사실이라고 믿는 경향인데, 이게 바로 인지 왜곡이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흑백논리 사고, 지나친 일반화, 개인화, 파국화, 감정적 추론, 독심술, 예언적 사고... 목록을 읽으면서 &quot;아, 이건 나다&quot; 싶은 패턴들이 여럿 있었다. 정신, 신체, 감정이 모두 연결되어 있기에 머릿속에서만 생각 과잉을 고치려 하면 안 된다는 저자의 지적도 납득이 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터를 읽고 난 후 대니얼 카너먼의 『생각에 관한 생각』을 펼쳤는데, 두 책이 같은 이야기를 하고 있다는 생각이 들었다. 카너먼은 인간의 사고를 빠른 직관(시스템 1)과 느린 이성(시스템 2)으로 나눠 설명하며, 우리가 얼마나 자주 편향과 휴리스틱에 속는지를 실험으로 보여준다. 『생각 중독』이 말하는 &quot;인지 왜곡&quot;은 결국 카너먼이 말하는 시스템 1의 오작동과 같은 현상이다. 차이가 있다면, 카너먼은 &quot;왜 인간의 사고는 이렇게 작동하는가&quot;를 탐구하고, 닉 트렌턴은 &quot;그래서 우리는 어떻게 해야 하는가&quot;를 다룬다. 두 책을 함께 읽고나니 문제의 구조와 해법을 동시에 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마지막 챕터: 다섯 가지 태도 전환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6장은 책 전체의 결론에 해당한다. 저자가 제시하는 다섯 가지 마음 전략은 담백하지만 강력하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;통제할 수 없는 일이 아니라, &lt;b&gt;통제할 수 있는 일&lt;/b&gt;에 집중한다&lt;/li&gt;
&lt;li&gt;할 수 없는 일이 아니라, &lt;b&gt;할 수 있는 일&lt;/b&gt;에 집중한다&lt;/li&gt;
&lt;li&gt;갖지 못한 것이 아니라, &lt;b&gt;가진 것&lt;/b&gt;에 집중한다&lt;/li&gt;
&lt;li&gt;과거나 미래가 아닌, &lt;b&gt;현재&lt;/b&gt;에 집중한다&lt;/li&gt;
&lt;li&gt;원하는 것이 아니라, &lt;b&gt;필요한 것&lt;/b&gt;에 집중한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 말들은 아니다. 들어본 적 없는 사람이 있을까. 하지만 이 책이 이 다섯 가지를 마지막에 배치한 이유는, 앞의 다섯 챕터를 통해 &lt;b&gt;왜 우리가 반대로 생각하게 되는지&lt;/b&gt;를 먼저 설명했기 때문이다. 기법이나 태도보다 먼저 메커니즘을 이해시키는 구조가 이 책의 가장 큰 미덕이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;읽고 나서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 읽기 전, 나는 내가 &quot;불안한 사람&quot;이라고 생각하지 않았다. 읽고 나서도 내가 불안한 사람인지 아닌지 단정할 수 없다. 다만, 내 머릿속의 소음을 &quot;불안&quot;이라는 렌즈로도 바라볼 수 있게 됐다는 건 분명하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 &quot;생각 과잉자의 다수는 실제로 무슨 일이 일어나는지 제대로 인식하지 못한 탓에 조종당한다&quot;는 문장이 계속 머릿속에 맴돈다. 문제를 인식하지 못하는 것이 문제라는 역설. 그 인식에서 모든 변화가 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리 자기계발서를 좋아하지 않는 편이었는데, 이 책은 기법을 나열하기 전에 왜 우리가 이렇게 생각하게 됐는지를 먼저 설명한다는 점에서 달랐다. 4.5점을 줬는데, 솔직히 0.5점을 깎은 특별한 근거는 없다. 만점을 주기 아까웠다는 게 더 정확하다. 아마도 그게 이 책을 가장 잘 설명하는 말일 수도 있다. 확실히 좋은 책인데, 만점을 줄 만큼 삶이 바뀌었냐고 묻는다면 아직은 모르겠다고 대답할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이런 분께 추천&lt;/b&gt;: 항상 머릿속이 시끄럽고 해야 할 일이 넘치는데 막상 아무것도 손에 잡히지 않는 분, 자신이 불안하다고 생각하지 않지만 어딘가 과부하 상태인 것 같은 분, 자기계발서가 뻔하다고 느끼는 분.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>생각중독</category>
      <category>서평</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/649</guid>
      <comments>https://insengnewbie.tistory.com/649#entry649comment</comments>
      <pubDate>Mon, 8 Jun 2026 10:30:34 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 개발자가 블로그도 잘 써야 하나요?</title>
      <link>https://insengnewbie.tistory.com/648</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출판사로부터 도서를 제공받아 작성한 후기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgvazr/dJMcaglsrVJ/naN3EokKtywkBaujiq2Q00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgvazr/dJMcaglsrVJ/naN3EokKtywkBaujiq2Q00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgvazr/dJMcaglsrVJ/naN3EokKtywkBaujiq2Q00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgvazr%2FdJMcaglsrVJ%2FnaN3EokKtywkBaujiq2Q00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;405&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;개발자가 블로그도 잘 써야 하나요? &amp;mdash; 글쓰기를 미뤄온 개발자에게 필요한 질문&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 코드를 잘 쓰면 충분할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사실 이 질문에 오래도록 &amp;ldquo;그렇다&amp;rdquo;고 답하고 싶었다. 개발자는 결국 문제를 해결하는 사람이고, 문제 해결의 결과물은 코드로 증명된다고 생각했기 때문이다. 블로그는 여유가 있는 사람들이 하는 부가 활동처럼 느껴졌고, 잘 정리된 글을 쓰는 일은 늘 우선순위 밖으로 밀려났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실무를 하다 보면 생각이 조금 달라진다. 코드를 작성하는 시간만큼이나 중요한 일이 &amp;ldquo;왜 이렇게 만들었는지&amp;rdquo; 설명하는 일이라는 것을 자주 마주하게 된다. 구현 방식의 근거를 남기고, 오류를 추적한 과정을 공유하고, 나중에 같은 문제를 다시 만났을 때 빠르게 맥락을 복구하는 일. 이 모든 과정은 결국 글쓰기와 연결되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『개발자가 블로그도 잘 써야 하나요?』는 바로 그 지점을 짚는 책이다. 단순히 &amp;ldquo;개발자라면 블로그를 운영하세요&amp;rdquo;라고 권하는 책이라기보다, 개발자가 왜 자신의 생각과 경험을 글로 남겨야 하는지 설득하는 책에 가깝다. 기술 블로그를 시작해야겠다고 생각만 하고 미뤄온 사람이라면, 이 책의 문제의식이 꽤 직접적으로 다가올 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;블로그는 보여주기용 포트폴리오만은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 블로그라고 하면 흔히 취업이나 이직을 위한 포트폴리오를 먼저 떠올린다. 물론 블로그가 커리어에 도움이 되는 것은 맞다. 어떤 문제를 어떻게 이해했고, 어떤 방식으로 해결했으며, 그 과정에서 무엇을 배웠는지를 글로 남기면 개발자의 사고 과정이 드러난다. 이력서나 깃허브만으로는 설명하기 어려운 부분을 블로그가 보완해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 책에서 더 인상 깊었던 부분은 블로그를 &amp;ldquo;나를 보여주는 도구&amp;rdquo; 이전에 &amp;ldquo;나를 정리하는 도구&amp;rdquo;로 바라본다는 점이었다. 개발하면서 마주친 문제는 당시에는 선명하지만, 시간이 지나면 쉽게 흐려진다. 왜 이 라이브러리를 선택했는지, 어떤 에러 때문에 시간을 썼는지, 어떤 해결책은 왜 포기했는지 같은 맥락은 기록하지 않으면 금방 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 비슷한 경험이 있다. 분명 예전에 해결했던 문제인데 몇 달 뒤 다시 마주하면 검색부터 다시 시작하게 된다. 그때 정리해둔 문서나 짧은 블로그 글이 있었다면 훨씬 빠르게 복구할 수 있었을 것이다. 결국 블로그는 타인을 위한 공유이면서 동시에 미래의 나를 위한 캐시가 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 시대에도 글쓰기가 중요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 AI가 코드도 작성하고, 문서 초안도 만들어준다. 그래서 오히려 글쓰기의 중요성이 줄어든 것처럼 보일 수 있다. 하지만 책을 읽으며 든 생각은 반대였다. AI가 초안을 만들어줄 수 있는 시대일수록, 개발자는 더 명확하게 질문하고 더 정확하게 판단할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 글은 단순히 문장을 예쁘게 다듬는 일이 아니다. 문제를 구조화하고, 맥락을 선별하고, 독자가 이해할 수 있는 순서로 재배치하는 작업이다. 이는 개발자가 코드를 설계할 때 필요한 사고와도 닮아 있다. 복잡한 요구사항을 나누고, 불필요한 부분을 걷어내고, 핵심 흐름을 명확하게 만드는 능력은 글쓰기에서도 그대로 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 활용해 글을 쓰더라도 결국 무엇을 말할지 결정하는 사람은 개발자 자신이다. 경험의 진위를 판단하고, 기술적 맥락을 보완하고, 자신의 관점을 더하는 일은 대체되기 어렵다. 그런 점에서 개발자의 블로그는 단순한 정보 게시물이 아니라, 자신의 사고 방식을 훈련하고 검증하는 공간이 될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하지 못한 사람에게 필요한 책&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 이미 글을 꾸준히 쓰는 사람보다, &amp;ldquo;써야 하는 건 알지만 막상 시작하지 못한 사람&amp;rdquo;에게 더 유용하다. 기술 블로그를 시작하려고 하면 대개 비슷한 장벽을 만난다. 너무 사소한 내용은 아닌지, 이미 누군가 쓴 글은 아닌지, 틀린 내용을 공개하면 부끄럽지 않을지 같은 고민이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 글이 완성도 높은 튜토리얼일 필요는 없다. 오늘 겪은 에러를 어떻게 해결했는지, 특정 기술을 왜 도입했는지, 공식 문서를 읽으며 이해한 내용을 어떻게 정리했는지부터 시작해도 충분하다. 중요한 것은 대단한 글을 쓰는 것이 아니라, 개발 과정에서 흘러가는 생각을 붙잡아두는 습관이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 글은 처음부터 잘 쓰기 어렵다. 코드처럼 글도 반복해서 작성하고 수정해야 나아진다. 다만 개발자에게 글쓰기는 선택적 취미라기보다, 자신의 경험을 자산으로 바꾸는 실무 능력에 가깝다. 이 책은 그 사실을 부담스럽지 않게 상기시켜준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『개발자가 블로그도 잘 써야 하나요?』라는 제목은 질문형이지만, 책을 읽고 나면 답은 꽤 분명해진다. 개발자가 반드시 유명한 블로거가 될 필요는 없다. 하지만 자신이 배운 것과 고민한 것을 글로 남기는 능력은 분명 개발자의 성장에 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 현재의 문제를 해결하지만, 글은 그 문제를 해결한 과정을 남긴다. 그리고 그 기록은 언젠가 나 자신에게도, 비슷한 문제를 만난 다른 개발자에게도 유효한 참고점이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 블로그를 시작해야겠다고 생각만 하고 미뤄왔다면, 이 책은 꽤 현실적인 출발점이 되어줄 수 있다. 개발자가 블로그도 잘 써야 하느냐는 질문에 대한 내 답은 이렇다. 꼭 &amp;ldquo;잘&amp;rdquo; 쓸 필요는 없지만, 꾸준히 &amp;ldquo;남길&amp;rdquo; 필요는 있다. 그리고 그 과정에서 개발자로서의 사고도 함께 단단해진다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>개발자가블로그도잘써야하나요</category>
      <category>길벗출판사</category>
      <category>서평</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/648</guid>
      <comments>https://insengnewbie.tistory.com/648#entry648comment</comments>
      <pubDate>Sun, 7 Jun 2026 22:30:45 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 타인과 일을 한다는 것</title>
      <link>https://insengnewbie.tistory.com/646</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lece3/dJMcacwevx5/k5YcHK7lAeCA8CVHeg6Amk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lece3/dJMcacwevx5/k5YcHK7lAeCA8CVHeg6Amk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lece3/dJMcacwevx5/k5YcHK7lAeCA8CVHeg6Amk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flece3%2FdJMcacwevx5%2Fk5YcHK7lAeCA8CVHeg6Amk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;505&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-heading=&quot;서로를 이해하지 못한다는 인정에서 시작하는 일&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-heading=&quot;서로를 이해하지 못한다는 인정에서 시작하는 일&quot;&gt;서로를 이해하지 못한다는 인정에서 시작하는 일&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『타인과 일을 한다는 것』을 읽으며 가장 오래 남은 문장은 &amp;ldquo;서로가 상대를 이해하지 못한다는 사실을 인정하는 것&amp;rdquo;이었다. 너무 당연한 말처럼 보이지만, 나는 이 당연한 사실을 자주 잊고 일해왔던 것 같다. 친해졌으니까 이해해줄 것이라고 생각했고, 같은 목표를 보고 있으니 당연히 말이 통할 것이라고 기대했다. 하지만 친밀함과 이해는 다른 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 조직에서 생기는 문제를 단순히 &amp;ldquo;누가 맞고 누가 틀렸는가&amp;rdquo;로 보지 않는다. 각자는 자기 자리에서 나름대로 합리적인 판단을 하고 있고, 그 합리성이 서로 다른 방향을 향할 때 문제가 생긴다고 말한다. 저자는 이것을 &amp;ldquo;내러티브&amp;rdquo;의 차이로 설명한다. 사람은 각자 자신이 놓인 환경, 책임, 경험, 역할 속에서 세상을 해석한다. 그래서 내 입장에서는 이상해 보이는 행동도 상대의 내러티브 안에서는 합리적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관점이 좋았다. 동시에 조금 아팠다. 나는 그동안 일하면서 상대가 왜 그런 판단을 하는지 충분히 이해하려 하지 않았던 것 같다. 내 기준에서 맞는 말, 효율적인 방법, 더 나은 방향이라고 생각하면 그것이 곧 정답처럼 느껴졌다. 그래서 상대가 동의하지 않으면 답답했고, 때로는 상대가 문제를 제대로 보지 못한다고 생각했다. 하지만 책을 읽고 나니 어쩌면 내가 보지 못한 것은 상대였을지도 모른다는 생각이 들었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 불완전함을 아는 사람만이 타인의 불완전함을 온전히 받아들일 수 있기 때문이다. 그리고 이러한 상호 인정과 이해의 과정 속에서 우리는 혼자가 아니라는 것을 깨닫는다. 우리 모두가 각자의 좁은 내러티브 안에서 살아가고 있다는 바로 그 사실이, 역설적으로 우리를 더욱 깊이 연결시켜 주는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 말하는 &amp;ldquo;대화&amp;rdquo;는 단순한 커뮤니케이션 기술이 아니다. 말을 부드럽게 하는 법도 아니고, 상대를 설득하는 요령도 아니다. 대화란 &lt;b&gt;나와 상대 사이에 놓인 골짜기를 알아차리고, 그곳에 다리를 놓는 실천&lt;/b&gt;이다. 이를 위해서는 먼저 &lt;b&gt;나의 내러티브를 잠시 옆에 두고&lt;/b&gt; 상대의 세계를 관찰해야 한다. &amp;ldquo;왜 저 사람은 저렇게 하지?&amp;rdquo;에서 멈추는 것이 아니라, &amp;ldquo;저 사람은 어떤 어려움 속에서 저렇게 판단하고 있을까?&amp;rdquo;라고 물어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에서 마셜 로젠버그의 『&lt;b&gt;비폭력대화&lt;/b&gt;』가 떠올랐다. 관찰과 평가를 분리하고, 판단보다 이해를 먼저 시도한다는 점에서 닮아 있었다. 다만 『타인과 일을 한다는 것』은 그 태도를 조직과 일의 언어로 옮겨 놓은 책에 가깝다. 회사에서 벌어지는 갈등, 총론에는 찬성하지만 각론에서는 반대하는 상황, 말하기 어려운 것을 억누르는 분위기, 권력과 역할이 만들어내는 거리감을 구체적으로 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 인상 깊었던 것은 &amp;ldquo;정론&amp;rdquo;만으로는 조직의 문제가 해결되지 않는다는 점이었다. 맞는 말을 하는 것과 일이 실제로 움직이는 것은 다르다. 아무리 좋은 제안이라도 그것이 상대에게 어떤 도움이 되는지, 회사의 현재 방향과 어떻게 연결되는지, 상대가 겪는 곤란을 어떻게 덜어주는지 설명되지 않으면 골짜기는 좁혀지지 않는다. 나 역시 일하면서 &amp;ldquo;좋은 말&amp;rdquo;을 했다고 생각했지만, 정작 상대의 맥락에 초대하지 못한 적이 많았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽으면서 저자에게는 일종의 인간찬가에 가까운 태도가 있다는 생각도 들었다. 다만 그것은 &amp;ldquo;사람은 모두 선하다&amp;rdquo;는 순진한 낙관은 아니다. 오히려 사람은 각자의 내러티브에 갇혀 서로를 오해하고, 때로는 상대를 도구처럼 대하며, 좋은 의도를 가지고도 조직 안에서 골짜기를 만든다는 사실을 인정한다. 그럼에도 저자는 대화를 통해 새로운 관계성을 만들 수 있다고 말한다. 불완전한 인간이 다시 서로에게 다가갈 수 있다는 믿음. 나는 그 점이 이 책의 가장 따뜻한 부분이라고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리적으로 생각해 봐도 이 세상에 중립적인 사람은 존재하지 않는다. 우리 모두는 각자의 내러티브를 갖고 살아간다는 의미에서 편향된 존재다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 대화를 낭만적으로만 말하지 않는다. 대화를 시도하다 보면 상대에게 영합할 수도 있고, 반대로 내 생각을 강요할 수도 있다. 너무 가까워져 다른 사람들과 멀어질 수도 있고, 아무 성과가 나지 않아 지칠 수도 있다. 그래서 저자는 대화가 목적 그 자체가 아니라는 점도 강조한다. 우리가 대화하는 이유는 조직 안에서 더 나은 일을 하기 위해서이고, 자신이 소중히 여기는 것을 지키기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 점에서 이 책은 조직론이면서 동시에 &lt;b&gt;삶의 태도&lt;/b&gt;에 관한 책처럼 읽혔다. 일을 단순히 돈을 버는 수단으로만 보지 않고, 조직 속에서 긍지 있게 산다는 것이 무엇인지 묻는다. 타인과 일한다는 것은 결국 계속해서 실패하고 오해하면서도 다시 관계를 만들어가는 일이다. 상대를 완전히 이해할 수 없다는 사실을 받아들이면서도, 이해하려는 시도를 포기하지 않는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4PeyY/dJMcahqLJrc/fK365gVGZUrS7pqfPGNKzk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4PeyY/dJMcahqLJrc/fK365gVGZUrS7pqfPGNKzk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4PeyY/dJMcahqLJrc/fK365gVGZUrS7pqfPGNKzk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4PeyY%2FdJMcahqLJrc%2FfK365gVGZUrS7pqfPGNKzk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;348&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-heading=&quot;이런 사람에게 추천하고 싶다&quot; data-ke-size=&quot;size26&quot;&gt;이런 사람에게 추천하고 싶다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 직장에서 사람 때문에 답답함을 느껴본 적 있는 사람에게 추천하고 싶다. 특히 &amp;ldquo;나는 맞는 말을 하고 있는데 왜 상대는 움직이지 않을까?&amp;rdquo;, &amp;ldquo;왜 저 사람은 저렇게 판단할까?&amp;rdquo;, &amp;ldquo;조직은 왜 이렇게 비합리적으로 굴러갈까?&amp;rdquo; 같은 생각을 자주 해본 사람이라면 얻어갈 것이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더나 매니저뿐 아니라, 실무자에게도 충분히 의미 있는 책이다. 조직의 문제를 해결하는 책임이 꼭 직책이 높은 사람에게만 있는 것은 아니기 때문이다. 우리가 매일 마주하는 동료, 상사, 다른 팀과의 관계 속에서도 작은 골짜기는 계속 생긴다. 이 책은 그 골짜기를 무시하거나 상대를 탓하는 대신, 어떻게 관찰하고 해석하고 다리를 놓을 수 있는지 생각하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천하는 이유는 이 책이 단순히 &amp;ldquo;대화를 잘하라&amp;rdquo;고 말하지 않기 때문이다. 대화를 친절한 말투나 원만한 분위기 정도로 축소하지 않고, 서로 다른 내러티브를 가진 사람들이 새로운 관계성을 만들어가는 실천으로 다룬다. 그래서 읽고 나면 당장 누군가를 설득하는 기술보다, 내가 상대를 어떤 시선으로 보고 있었는지를 먼저 돌아보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 이 책은 함께 일하는 일을 조금 덜 냉소적으로 보게 만든다. 조직에는 답답한 일도 많고, 말이 통하지 않는 순간도 많다. 하지만 그 안에서도 상대의 맥락을 보려는 시도, 내 관점을 잠시 내려놓는 태도, 다시 대화를 시작하려는 선택은 가능하다. 그런 점에서 이 책은 더 좋은 조직을 만들고 싶은 사람뿐 아니라, 일 속에서 스스로를 잃지 않고 싶은 사람에게도 추천하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나에게 이 책은 대화에 대한 책이기 전에, 내가 얼마나 내 관점 안에 갇혀 있었는지를 돌아보게 한 책이었다. 타인과 일한다는 것은 내 뜻대로 움직이지 않는 사람들과 부딪히는 일이 아니라, 서로 다른 세계를 가진 사람들과 다리를 놓는 일이다. 그리고 그 다리를 놓는 과정에서, 우리는 조금 더 나은 동료가 되고 조금 더 자신의 일의 주인공이 될 수 있는 것 같다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>대화</category>
      <category>대화론</category>
      <category>독서후기</category>
      <category>비폭력대화</category>
      <category>타인과 일을 한다는 것</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/646</guid>
      <comments>https://insengnewbie.tistory.com/646#entry646comment</comments>
      <pubDate>Mon, 4 May 2026 00:56:22 +0900</pubDate>
    </item>
    <item>
      <title>[서평] 유난한 도전 - 팀이 커지면 생기는 것들</title>
      <link>https://insengnewbie.tistory.com/645</link>
      <description>&lt;h2&gt;반대 의견이 사라지는 순간&lt;/h2&gt;
&lt;p&gt;토스팀이 100명을 넘겼을 때, 이상한 일이 벌어졌다.&lt;/p&gt;
&lt;p&gt;공론장에서 이승건의 말이나 결정에 반대하는 의견이 줄어들기 시작한 것이다. 그리고 어느새 조직은 둘로 나뉘었다. 이승건에게 직접 말을 걸 수 있는 사람, 그렇지 못한 사람.&lt;/p&gt;
&lt;p&gt;책은 이 장면을 &amp;quot;위험 신호&amp;quot;라고 표현한다. 나는 이 문장을 읽는 순간, 잠시 책을 내려놓았다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;리더에게 반대가 줄어드는 순간이 조직의 위험 신호다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;이 한 문장이 스타트업에 몸담고 있는 사람이라면 누구든 한 번쯤 본 풍경처럼 느껴질 것 같아서.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;처음엔 모두가 뛰었다&lt;/h2&gt;
&lt;p&gt;토스의 초기는 혼돈이었다. 공인인증서 없이 간편 송금을 만들겠다는 아이디어는 8번이나 거절당했다. 페이팔로부터 국내 최초 투자를 받기까지의 과정도 순탄치 않았다.&lt;/p&gt;
&lt;p&gt;그럼에도 팀은 &lt;strong&gt;&amp;quot;다다다다 전략&amp;quot;&lt;/strong&gt; 을 택했다. 천천히 하나씩 시도할 여유가 없으니, 모두 다 빠르게 실험해보자는 것이었다. 실패파티를 열어 실패를 공유하고 축하했다. 장애가 나면 괴로워하면서도, 같은 장애에 두 번 당하지 않기 위해 더 빠르고 유연한 대처법을 만들었다.&lt;/p&gt;
&lt;p&gt;이 시기의 토스팀에게는 공통된 에너지가 있었다. &amp;quot;이렇게 끝내고 싶지 않다&amp;quot;는 강렬한 욕망, 그리고 그것을 함께 붙들고 뛰는 사람들.&lt;/p&gt;
&lt;p&gt;책에는 이런 문장이 나온다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&amp;quot;경험상 적이 없는 나이스가이는 어떤 것도 스스로 결정하는 법이 없는 무능한 사람이었다. 판을 흔들어 무언가를 바꿔보려는 혁신가는 적이 많았다.&amp;quot;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;빠르게 실패하고, 빠르게 배우고, 빠르게 다음으로 넘어가는 것. 그것이 작은 팀이 살아남는 방식이었다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;팀이 커지면 생기는 것들&lt;/h2&gt;
&lt;p&gt;문제는 팀이 성장하면서 시작된다.&lt;/p&gt;
&lt;p&gt;초기에는 제너럴리스트가 필요하다. 불확실하고 빠르게 변하는 환경에서, 한 사람이 여러 역할을 유연하게 소화해야 한다. 그런데 팀이 어느 정도 크면, 스페셜리스트가 필요해진다. 이 전환 과정에서 초기 팀원들이 하나둘 떠나갔다.&lt;/p&gt;
&lt;p&gt;공론장도 달라진다. 처음엔 누구나 무슨 말이든 할 수 있었다. 하지만 100명을 넘기자, 리더에게 직설적으로 말하는 사람이 줄었다. 사소한 불만도, 반대 의견도 점차 들리지 않게 됐다.&lt;/p&gt;
&lt;p&gt;이것이 위험한 이유는 단순하다. 리더가 조직의 실제 상태를 보지 못하게 되기 때문이다.&lt;/p&gt;
&lt;p&gt;토스는 결국 팀원의 직설로 이 문제를 수면 위로 끌어올렸다. 이승건은 전체 앞에서 말했다. &amp;quot;무슨 이야기든 사소한 이야기든 직접 이야기해달라. 한 번만 더 나를 믿어달라.&amp;quot; 그리고 C레벨을 해체하며 더 수평적인 조직으로 나아갔다.&lt;/p&gt;
&lt;p&gt;책에는 이런 문장도 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&amp;quot;유능한 인재의 99%는 제한 없이 신뢰받는다고 느낄 때 훨씬 더 역량을 발휘한다.&amp;quot;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;책을 덮고 나서 남은 질문들&lt;/h2&gt;
&lt;p&gt;토스의 이야기를 따라가다 보면, 어느 순간 자연스럽게 스스로를 돌아보게 된다.&lt;/p&gt;
&lt;p&gt;나는 지금 빠르게 실패하고 있는가, 아니면 실패를 피하고 있는가.&lt;/p&gt;
&lt;p&gt;나는 제너럴리스트로 뛰어야 할 시기에 있는가, 스페셜리스트로 깊어져야 할 시기인가.&lt;/p&gt;
&lt;p&gt;나는 리더에게, 혹은 팀원에게 하고 싶은 말을 실제로 하고 있는가.&lt;/p&gt;
&lt;p&gt;이 질문들에 자신 있게 &amp;quot;그렇다&amp;quot;고 답하기가 쉽지 않다. 그리고 그 불편함이, 이 책이 남긴 가장 솔직한 흔적인 것 같다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정답보다 중요한 것&lt;/h2&gt;
&lt;p&gt;토스가 모든 문제를 멋지게 풀어낸 건 아니다. 100명의 벽에서 흔들렸고, 야수성을 잃어버린 시기도 있었다. 그럼에도 이승건은 기회가 될 때마다 동료들에게 같은 말을 반복했다고 한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;우리는 미친 것처럼 보이는 꿈을 꾸지만 결국 그 꿈을 현실로 만들어낼 것이다.&amp;quot;&lt;/p&gt;
&lt;p&gt;뒷말은 생략한 채였다. &amp;quot;그러지 않으면 우리는 죽을 테니까.&amp;quot;&lt;/p&gt;
&lt;p&gt;팀이 커지면 생기는 것들이 있다. 그리고 그만큼 사라지는 것들도 있다. 이 책은 그 사라지는 것들에 이름을 붙여준다. 이름을 알면, 적어도 잃어버리고 있다는 걸 알아챌 수 있다.&lt;/p&gt;
&lt;p&gt;그것만으로도 충분히 읽을 이유가 있는 책이었다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>유난한도전</category>
      <category>토스</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/645</guid>
      <comments>https://insengnewbie.tistory.com/645#entry645comment</comments>
      <pubDate>Mon, 27 Apr 2026 18:05:18 +0900</pubDate>
    </item>
    <item>
      <title>[FE] Next.js App Router에서 dynamic import를 제대로 이해하기</title>
      <link>https://insengnewbie.tistory.com/644</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dynamic import&lt;/code&gt;를 설명할 때 흔히 나오는 문장은 대체로 비슷합니다. 번들을 쪼개고, 처음에 내려보내는 JavaScript를 줄이고, 필요할 때만 코드를 받는다는 설명입니다. 틀린 말은 아닙니다. 다만 App Router에서는 이 설명만으로는 부족합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Router에서 &lt;code&gt;dynamic import&lt;/code&gt;는 단순히 &quot;코드를 나중에 받는 기능&quot;이라기보다, 클라이언트 컴포넌트 경계를 어떤 단위로 늦게 활성화할지 정하는 도구에 가깝습니다. 그래서 이 기능을 이해할 때는 번들 크기만 볼 것이 아니라, 초기 HTML에 무엇이 보이는지, RSC payload에 무엇이 남는지, hydration이 어디서부터 시작되는지를 함께 봐야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 그 관점에서 &lt;code&gt;import()&lt;/code&gt;, &lt;code&gt;next/dynamic&lt;/code&gt;, Suspense, hydration, &lt;code&gt;ssr: true&lt;/code&gt;, &lt;code&gt;ssr: false&lt;/code&gt;를 하나의 흐름으로 묶어 설명하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;code&gt;import()&lt;/code&gt;와 &lt;code&gt;next/dynamic&lt;/code&gt;은 같은 lazy loading이 아닙니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 가장 헷갈리기 쉬운 두 가지를 분리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 &lt;code&gt;import()&lt;/code&gt;는 모듈을 실행 시점에 가져오는 JavaScript 기능입니다. 보통 이벤트가 발생했을 때만 필요한 라이브러리를 늦게 읽는 데 잘 맞습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'

import { useState } from 'react'

export default function SearchBox() {
  const [results, setResults] = useState&amp;lt;string[]&amp;gt;([])

  async function handleChange(value: string) {
    const Fuse = (await import('fuse.js')).default
    const fuse = new Fuse(['dynamic import', 'Suspense', 'hydration'])
    setResults(fuse.search(value).map((item) =&amp;gt; item.item))
  }

  return &amp;lt;input onChange={(e) =&amp;gt; handleChange(e.target.value)} /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우의 핵심은 컴포넌트가 아니라 라이브러리 지연 로드입니다. 사용자가 입력하기 전까지는 &lt;code&gt;fuse.js&lt;/code&gt;를 브라우저에 보낼 필요가 없으므로, 초기 JavaScript를 줄이는 효과가 분명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;code&gt;next/dynamic&lt;/code&gt;은 React 컴포넌트를 지연 로드하기 위한 Next.js 레이어입니다. 내부적으로는 &lt;code&gt;React.lazy()&lt;/code&gt;와 Suspense 모델 위에 올라가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'

import dynamic from 'next/dynamic'

const Chart = dynamic(() =&amp;gt; import('./Chart'))

export default function ChartSection() {
  return &amp;lt;Chart /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 App Router에서 이 기능의 직접적인 대상이 대체로 Client Component라는 사실입니다. Server Component는 원래 브라우저 번들의 직접 대상이 아닙니다. 따라서 &lt;code&gt;dynamic import&lt;/code&gt;의 실질적인 성능 효과는 대부분 클라이언트에 내려가는 코드와 hydration 비용을 늦추는 데서 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. App Router에서는 HTML 하나만 렌더되는 것이 아닙니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Router에서 초기 로드를 이해하려면 결과물을 세 가지로 나눠 봐야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) HTML&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 가장 먼저 보는 비인터랙티브 프리뷰입니다. 빠르게 화면 형태를 보여 주는 역할을 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) RSC payload&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 컴포넌트 트리의 결과와, 클라이언트 컴포넌트가 어디에 들어와야 하는지에 대한 참조 정보를 담은 직렬화 결과입니다. 여기에는 서버 컴포넌트의 렌더 결과, 클라이언트 컴포넌트 자리 표시자, 해당 JavaScript 참조, 전달할 props가 포함됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Client JavaScript&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client Component를 hydrate하고 이벤트를 붙여 실제 인터랙션을 활성화하는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 초기 렌더는 &quot;서버가 HTML 하나를 만들고 끝&quot;이 아닙니다. 서버는 먼저 Server Component를 RSC payload로 만들고, Next.js는 그 payload와 클라이언트 측 정보를 이용해 HTML 프리뷰를 만듭니다. 그리고 브라우저는 HTML을 즉시 보여 준 뒤, RSC payload로 트리를 맞추고, 마지막으로 Client Component JavaScript를 실행해 hydration을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 이해하면 &lt;code&gt;dynamic import&lt;/code&gt;를 보는 시선도 달라집니다. 이 기능은 HTML만 바꾸는 것도 아니고, 단순히 파일 하나를 늦게 받는 것도 아닙니다. 클라이언트 경계의 코드 로드와 hydration 시점을 조정하는 쪽에 더 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. App Router에서 &lt;code&gt;dynamic import&lt;/code&gt;는 lazy한 클라이언트 경계를 심는 일입니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예시는 App Router에서 비교적 안전한 패턴입니다. Server Component가 페이지의 큰 구조를 만들고, 실제 지연 로드 경계는 Client Component 안에서 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/dashboard/page.tsx
import ReportPanel from './ReportPanel'

export default async function Page() {
  const data = await getDashboardData()
  return &amp;lt;ReportPanel data={data} /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/dashboard/ReportPanel.tsx
'use client'

import { Suspense } from 'react'
import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() =&amp;gt; import('./HeavyChart'))

export default function ReportPanel({ data }) {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h2&amp;gt;주간 리포트&amp;lt;/h2&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;ChartSkeleton /&amp;gt;}&amp;gt;
        &amp;lt;HeavyChart data={data} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/section&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 서버는 &lt;code&gt;HeavyChart&lt;/code&gt;를 브라우저에서 바로 실행하지 않습니다. 서버가 남기는 것은 &quot;여기에 이 Client Component가 들어와야 한다&quot;는 경계 정보와 props입니다. 그리고 브라우저는 관련 JavaScript가 준비되면 그 경계를 hydrate합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관점에서 보면 &lt;code&gt;dynamic import&lt;/code&gt;는 &quot;서버에서 나중에 렌더한다&quot;가 아니라, &quot;클라이언트 경계를 늦게 활성화한다&quot;로 이해하는 편이 정확합니다. 핵심은 실행 시점보다 경계의 활성화 시점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[!warning]&lt;br /&gt;구현상 주의할 점도 있습니다. Next.js 공식 문서는 Server Component가 Client Component를 &lt;code&gt;dynamic()&lt;/code&gt;으로 감쌀 때 자동 코드 스플리팅이 현재 완전하게 지원되지 않는다고 안내합니다. 따라서 실제로 클라이언트 번들을 늦게 받고 싶다면, 위 예시처럼 &lt;code&gt;dynamic()&lt;/code&gt; 경계를 Client Component 쪽에 두는 편이 안전합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 자주 놓치는 사실이 있습니다. &lt;code&gt;dynamic()&lt;/code&gt;을 썼다고 해서 초기 HTML이 반드시 비는 것은 아닙니다. &lt;code&gt;ssr&lt;/code&gt;를 끄지 않았다면 Client Component는 기본적으로 서버 프리렌더 대상이 될 수 있습니다. 다시 말해, 사용자는 먼저 HTML 프리뷰를 볼 수 있고, 실제 상호작용은 나중에 hydrate되며 붙습니다. 이 지점이 &lt;code&gt;dynamic import = 화면이 늦게 뜬다&lt;/code&gt;라는 단순화와 다른 부분입니다. &lt;code&gt;ssr: true&lt;/code&gt;도 동일하게 동작합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Suspense boundary&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspense boundary는 로딩 UI가 아니라 streaming과 hydration의 단위입니다. &lt;code&gt;dynamic import&lt;/code&gt;를 제대로 이해하려면 Suspense를 함께 봐야 합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'

import { Suspense } from 'react'
import dynamic from 'next/dynamic'

const CommentsPanel = dynamic(() =&amp;gt; import('./CommentsPanel'))

export default function ArticleExtras() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;댓글 영역을 준비 중입니다.&amp;lt;/p&amp;gt;}&amp;gt;
      &amp;lt;CommentsPanel /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 분들이 Suspense를 단순히 loading UI를 붙이는 도구로 생각합니다. 그러나 App Router에서 Suspense boundary는 그보다 훨씬 중요한 역할을 합니다. 이 경계는 서버 스트리밍 단위이기도 하고, 클라이언트 hydration 분할 단위이기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 경계가 아직 준비되지 않았다면 서버는 우선 fallback을 보내고, 준비가 끝난 뒤 그 경계만 따로 이어서 보낼 수 있습니다. 브라우저 쪽에서도 React는 같은 경계를 기준으로 hydration 작업을 쪼갤 수 있습니다. 이것이 selective hydration 관점입니다. 준비된 부분부터 먼저 인터랙션을 붙이고, 아직 코드나 데이터가 준비되지 않은 부분은 뒤로 미룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;dynamic import&lt;/code&gt;가 하는 일은 hydration을 없애는 것이 아닙니다. hydration 대상을 더 작은 단위로 나누고, 특정 단위의 시작 시점을 늦추는 것입니다. 이 차이를 이해하면, 왜 Suspense boundary 설계가 성능보다 체감 UX에 더 큰 영향을 주는지 설명이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경계를 너무 크게 잡으면 작은 지연이 화면 전체의 지연처럼 느껴집니다. 반대로 경계를 지나치게 잘게 쪼개면 fallback이 곳곳에서 번쩍이며 오히려 거슬릴 수 있습니다. 좋은 boundary는 코드 구조 기준이 아니라 사용자가 기다림을 인식하는 단위 기준으로 잡아야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. &lt;code&gt;ssr: true&lt;/code&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Client Component부터 보겠습니다. App Router에서 &lt;code&gt;next/dynamic&lt;/code&gt;은 &lt;code&gt;React.lazy()&lt;/code&gt;와 Suspense 모델 위에 올라가며, 이때 Client Component는 기본적으로 서버에서 프리렌더될 수 있습니다. 즉, &lt;code&gt;ssr: true&lt;/code&gt;를 명시한다고 해서 특별한 최적화가 새로 생기는 것이 아니라, 원래의 기본 동작을 드러내는 쪽에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'

import dynamic from 'next/dynamic'

const PurchasePanel = dynamic(() =&amp;gt; import('./PurchasePanel'), {
  ssr: true,
})

export default function ProductAside() {
  return &amp;lt;PurchasePanel /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 서버는 &lt;code&gt;PurchasePanel&lt;/code&gt;의 HTML 프리뷰를 먼저 만들 수 있습니다. 그러나 여기서 자주 생기는 오해가 하나 있습니다. &lt;code&gt;ssr: true&lt;/code&gt;는 &quot;이 컴포넌트는 서버에서만 끝난다&quot;는 뜻이 아닙니다. HTML 프리뷰가 먼저 보일 뿐이고, 브라우저는 여전히 관련 JavaScript를 받아 이 경계를 hydrate해야 합니다. 다시 말해, &lt;code&gt;ssr: true&lt;/code&gt;는 hydration을 없애는 옵션이 아니라, 프리렌더를 유지한 채 클라이언트 경계를 나중에 활성화하는 기본 모델을 설명하는 표현입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무적으로도 그래서 &lt;code&gt;ssr: true&lt;/code&gt;를 직접 적는 경우는 많지 않습니다. 옵션을 생략해도 같은 동작이기 때문입니다. 다만 &quot;이 컴포넌트는 서버 프리뷰를 유지한 채 지연 로드하겠다&quot;는 의도를 문서화하려는 목적이라면 명시적으로 적어 둘 수는 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Server Component를 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/article/page.tsx
import dynamic from 'next/dynamic'

const RelatedProducts = dynamic(() =&amp;gt; import('./RelatedProducts'))

export default async function Page() {
  const article = await getArticle()

  return (
    &amp;lt;main&amp;gt;
      &amp;lt;ArticleBody article={article} /&amp;gt;
      &amp;lt;RelatedProducts /&amp;gt;
    &amp;lt;/main&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;code&gt;RelatedProducts&lt;/code&gt;가 Server Component라면, 설명의 중심축은 더 이상 &quot;&lt;code&gt;ssr: true&lt;/code&gt;인가 아닌가&quot;가 아닙니다. Server Component는 원래 서버에서 렌더되는 존재이기 때문입니다. Next.js 공식 문서가 강조하듯, Server Component를 &lt;code&gt;dynamic()&lt;/code&gt;으로 가져와도 lazy loading의 직접 대상은 그 컴포넌트 자체가 아닙니다. 실제로 늦게 활성화되는 것은 그 하위의 Client Component들이고, 함께 쓰이는 CSS 같은 정적 asset preload에도 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 같은 &lt;code&gt;dynamic()&lt;/code&gt;이라도 질문이 달라집니다. Client Component를 대상으로 할 때는 &quot;서버 프리뷰를 유지할 것인가, 포기할 것인가&quot;가 핵심입니다. 반면 Server Component를 대상으로 할 때는 &quot;이 서버 컴포넌트 아래의 클라이언트 경계와 asset 준비를 어떻게 다룰 것인가&quot;가 더 정확한 질문입니다. 이 차이를 놓치면 &lt;code&gt;ssr: true&lt;/code&gt;를 &lt;code&gt;ssr: false&lt;/code&gt;의 반대 옵션처럼 오해하게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. &lt;code&gt;ssr: false&lt;/code&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ssr: false&lt;/code&gt;는 자주 오해되는 옵션입니다. 이름만 보면 서버 렌더 비용을 줄이는 최적화처럼 보입니다. 하지만 실질적으로는 &quot;이 컴포넌트를 서버에서 미리 보여 주지 않겠다&quot;는 선택에 더 가깝습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'

import dynamic from 'next/dynamic'

const MapView = dynamic(() =&amp;gt; import('./MapView'), {
  ssr: false,
})

export default function StoreLocator() {
  return &amp;lt;MapView /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정을 쓰면 해당 컴포넌트는 서버 HTML 프리뷰에 실리지 않습니다. 브라우저가 JavaScript를 받고 실행한 뒤에야 실제 UI가 마운트됩니다. &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;document&lt;/code&gt;, 지도 SDK, 에디터처럼 브라우저 환경에 강하게 의존하는 컴포넌트라면 매우 유용합니다. 다만 대가도 분명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 초기 페인트 관점에서는 손해일 수 있습니다. 사용자가 즉시 볼 수 있는 프리뷰가 줄어들기 때문입니다. 둘째, SEO가 중요한 핵심 콘텐츠에는 불리할 수 있습니다. 셋째, 사용자가 보게 되는 것은 &quot;최적화된 화면&quot;이 아니라 &quot;오래 남아 있는 빈 영역 또는 스켈레톤&quot;일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;ssr: false&lt;/code&gt;는 성능 최적화 옵션이 아니라 호환성 도구에 가깝습니다. 서버에서 렌더할 수 없는 UI를 다루기 위한 수단이지, 핵심 화면을 무조건 빠르게 만들어 주는 장치가 아닙니다. 그리고 App Router에서는 이 옵션이 Server Component에서 허용되지 않는다는 점도 함께 기억해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 왜 bundle size 감소가 체감 성능 향상으로 바로 이어지지 않을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터가 실무에서 가장 중요한 부분입니다. 번들을 분리했는데도 사용자가 빨라졌다고 느끼지 못하는 경우가 많기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달은 &lt;code&gt;dynamic import&lt;/code&gt;의 전형적인 성공 사례입니다. 초기 렌더에는 필요 없고, 사용자가 버튼을 눌렀을 때만 필요합니다. 이 경우에는 다운로드, 파싱, hydration을 뒤로 미루는 판단이 곧바로 합리적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 첫 화면의 무거운 차트는 이야기가 다릅니다. 차트 라이브러리를 분리해도 사용자가 페이지에 들어오자마자 차트를 봐야 한다면, 결국 그 청크는 곧 필요해집니다. 이때 병목은 다운로드가 아니라 차트 라이브러리 초기화, 캔버스 렌더링, effect 실행, hydration 비용일 수 있습니다. 그러면 번들은 줄었는데 체감 속도는 크게 좋아지지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도나 에디터는 또 다른 케이스입니다. 브라우저 API와 외부 SDK 의존 때문에 &lt;code&gt;ssr: false&lt;/code&gt;가 필요할 수 있습니다. 하지만 이것을 첫 화면 핵심 정보로 두면 사용자는 오랫동안 빈 박스나 스켈레톤을 보게 됩니다. 기술적으로는 맞는 선택인데, UX 관점에서는 손해가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 lazy loading은 network waterfall을 만들 수도 있습니다. 부모가 먼저 hydrate되어야 자식 경계의 로드가 본격화되거나, 사용자 상호작용 이후에야 요청이 나가는 구조라면, 번들을 쪼갠 대가로 대기 구간이 늘어날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 중요한 질문은 &quot;몇 KB를 줄였는가&quot;가 아니라 &quot;이 UI가 언제 필요하고, 그 시점까지 어떤 경험을 보여 줄 것인가&quot;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 실무에서는 무엇을 기준으로 판단하면 좋을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 &lt;code&gt;dynamic import&lt;/code&gt; 도입 여부는 다음 질문으로 판단하는 편이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 컴포넌트가 초기 viewport에 꼭 필요한가&lt;/li&gt;
&lt;li&gt;병목이 다운로드인가, hydration인가, 데이터 fetch인가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dynamic()&lt;/code&gt;보다 Server/Client 경계 재설계가 더 큰 효과를 내지는 않는가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssr: true&lt;/code&gt;를 적고 싶은 이유가 실제 동작 변경인지, 아니면 기본 프리렌더 의도를 드러내려는 것인지&lt;/li&gt;
&lt;li&gt;fallback이 실제 사용자 경험에서 자연스러운가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssr: false&lt;/code&gt;가 필요한 이유가 성능이 아니라 브라우저 의존성 때문은 아닌가&lt;/li&gt;
&lt;li&gt;지연 로드 이후 network waterfall이 생기지 않는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 대체로 다음처럼 생각하면 판단이 단순해집니다. 초기 화면 밖의 무거운 인터랙티브 UI, 클릭 후 열리는 모달, 조건부로만 보이는 패널은 &lt;code&gt;dynamic import&lt;/code&gt;와 잘 맞습니다. 반대로 첫 화면의 핵심 콘텐츠는 무작정 lazy하게 만들기보다 Server Component 비중을 늘리거나 Client Component 경계를 더 작게 줄이는 쪽이 더 큰 효과를 내는 경우가 많습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Router에서 &lt;code&gt;dynamic import&lt;/code&gt;는 단순한 번들 테크닉이 아닙니다. 더 정확히 말하면, 클라이언트 경계를 언제 어떤 단위로 활성화할지 설계하는 장치입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 기능을 볼 때는 &quot;청크를 나눴는가&quot;만 보지 말고, 초기 HTML에 무엇이 보이는지, RSC payload에 어떤 참조가 남는지, Suspense boundary가 어떤 단위로 streaming과 hydration을 나누는지 함께 봐야 합니다. 그 관점이 생기면 &lt;code&gt;ssr: true&lt;/code&gt;를 기본 프리렌더 모델로 이해하게 되고, &lt;code&gt;ssr: false&lt;/code&gt;를 무심코 붙이는 일도 줄며, 왜 번들 감소가 곧바로 UX 개선으로 이어지지 않는지도 훨씬 선명하게 이해하게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;더 읽을 거리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/guides/lazy-loading&quot;&gt;Next.js Lazy Loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/getting-started/server-and-client-components&quot;&gt;Next.js Server and Client Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/Suspense&quot;&gt;React Suspense&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react-dom/server/renderToReadableStream&quot;&gt;React Server Rendering API: renderToReadableStream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/644</guid>
      <comments>https://insengnewbie.tistory.com/644#entry644comment</comments>
      <pubDate>Tue, 31 Mar 2026 23:47:10 +0900</pubDate>
    </item>
    <item>
      <title>소설 &amp;quot;스토너&amp;quot; 후기</title>
      <link>https://insengnewbie.tistory.com/643</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crnZqO/dJMcadBo309/rCns610Al0VasOSnsgYbF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crnZqO/dJMcadBo309/rCns610Al0VasOSnsgYbF0/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crnZqO/dJMcadBo309/rCns610Al0VasOSnsgYbF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrnZqO%2FdJMcadBo309%2FrCns610Al0VasOSnsgYbF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JhN8U/dJMcai3NCVh/tai0byDonatlyHkiWK0q9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JhN8U/dJMcai3NCVh/tai0byDonatlyHkiWK0q9k/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JhN8U/dJMcai3NCVh/tai0byDonatlyHkiWK0q9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJhN8U%2FdJMcai3NCVh%2Ftai0byDonatlyHkiWK0q9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막을&amp;nbsp;읽는&amp;nbsp;내내&amp;nbsp;애잔한&amp;nbsp;감정이&amp;nbsp;가슴에&amp;nbsp;머물렀고,&amp;nbsp;책을&amp;nbsp;덮은&amp;nbsp;이&amp;nbsp;순간까지도&amp;nbsp;먹먹함이&amp;nbsp;남아있다.&amp;nbsp;윌리엄&amp;nbsp;스토너의&amp;nbsp;인생은&amp;nbsp;성공이나&amp;nbsp;실패로&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;없으며,&amp;nbsp;그저&amp;nbsp;그&amp;nbsp;자신으로&amp;nbsp;인생을&amp;nbsp;살았다는&amp;nbsp;생각이&amp;nbsp;든다.&amp;nbsp;&amp;ldquo;너는&amp;nbsp;무엇을&amp;nbsp;기대했나?&amp;rdquo;.&amp;nbsp;스토너가&amp;nbsp;죽음&amp;nbsp;직전&amp;nbsp;몇번이고&amp;nbsp;되뇌이는&amp;nbsp;말이다.&amp;nbsp;마음에&amp;nbsp;와닿다&amp;nbsp;못해&amp;nbsp;나의&amp;nbsp;뇌와&amp;nbsp;심장에&amp;nbsp;박히는&amp;nbsp;듯했다.&lt;br /&gt;&lt;br /&gt;스토너는&amp;nbsp;자신이&amp;nbsp;기대했던&amp;nbsp;것이&amp;nbsp;채워짐&amp;nbsp;대한&amp;nbsp;충족감과&amp;nbsp;행복감보다&amp;nbsp;자신의&amp;nbsp;삶을&amp;nbsp;채웠던&amp;nbsp;열정,&amp;nbsp;기쁨,&amp;nbsp;슬픔,&amp;nbsp;이별을&amp;nbsp;되돌아보며&amp;nbsp;충만했던&amp;nbsp;자신의&amp;nbsp;삶에&amp;nbsp;대해&amp;nbsp;만족하며&amp;nbsp;눈을&amp;nbsp;감았다.&amp;nbsp;그러나&amp;nbsp;우리는&amp;nbsp;일상&amp;nbsp;속에서&amp;nbsp;당연하다는&amp;nbsp;듯이&amp;nbsp;&amp;ldquo;성공&amp;rdquo;이나&amp;nbsp;&amp;ldquo;행복&amp;rdquo;에&amp;nbsp;대해&amp;nbsp;말하고&amp;nbsp;그것을&amp;nbsp;기대한다.&amp;nbsp;하지만&amp;nbsp;우리의&amp;nbsp;삶의&amp;nbsp;마지막에&amp;nbsp;떠오르는&amp;nbsp;것은&amp;nbsp;일순간의&amp;nbsp;&amp;ldquo;성공&quot;과&amp;nbsp;&amp;ldquo;행복&amp;rdquo;&amp;nbsp;보다는&amp;nbsp;자신의&amp;nbsp;삶을&amp;nbsp;가득&amp;nbsp;채운&amp;nbsp;충만감일&amp;nbsp;것이다.&amp;nbsp;나는&amp;nbsp;단순히&amp;nbsp;대학&amp;nbsp;합격의&amp;nbsp;기쁨,&amp;nbsp;취업&amp;nbsp;성공의&amp;nbsp;기쁨,&amp;nbsp;배달&amp;nbsp;음식의&amp;nbsp;소확행&amp;nbsp;뿐만&amp;nbsp;아니라&amp;nbsp;실패에&amp;nbsp;대한&amp;nbsp;고통,&amp;nbsp;이별의&amp;nbsp;슬픔,&amp;nbsp;혼자라는&amp;nbsp;고독감&amp;nbsp;등&amp;nbsp;삶을&amp;nbsp;다채롭게&amp;nbsp;칠해주는&amp;nbsp;감정들이&amp;nbsp;모여서&amp;nbsp;삶을&amp;nbsp;충만하게&amp;nbsp;만들어준다고&amp;nbsp;생각한다.&amp;nbsp;내가&amp;nbsp;보기에&amp;nbsp;스토너의&amp;nbsp;인생은&amp;nbsp;정말이지&amp;nbsp;&amp;ldquo;충만&amp;rdquo;했다고&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;문학과의&amp;nbsp;사랑에&amp;nbsp;빠지고,&amp;nbsp;친우의&amp;nbsp;죽음에&amp;nbsp;슬퍼하고,&amp;nbsp;성급히&amp;nbsp;첫사랑과&amp;nbsp;결혼했다가&amp;nbsp;결혼의&amp;nbsp;실패를&amp;nbsp;인정하기도&amp;nbsp;하며,&amp;nbsp;교수로서&amp;nbsp;학생들을&amp;nbsp;가르치는데&amp;nbsp;열정을&amp;nbsp;쏟기도&amp;nbsp;했다.&amp;nbsp;그는&amp;nbsp;언제나&amp;nbsp;&amp;ldquo;윌리엄&amp;nbsp;스토너&amp;rdquo;로서&amp;nbsp;살았고&amp;nbsp;그답게&amp;nbsp;충만한&amp;nbsp;인생의&amp;nbsp;막을&amp;nbsp;내렸다.&amp;nbsp;&amp;ldquo;삶이란&amp;nbsp;이런&amp;nbsp;것이지&amp;rdquo;라는&amp;nbsp;생각이&amp;nbsp;들기도&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;우리는&amp;nbsp;24시간&amp;nbsp;내내&amp;nbsp;행복할&amp;nbsp;수도&amp;nbsp;없으며&amp;nbsp;24시간&amp;nbsp;내내&amp;nbsp;슬퍼해서도&amp;nbsp;안&amp;nbsp;된다.&amp;nbsp;인생의&amp;nbsp;단맛과&amp;nbsp;쓴맛&amp;nbsp;모두&amp;nbsp;삶을&amp;nbsp;다채롭게&amp;nbsp;만들어주는&amp;nbsp;요소임을&amp;nbsp;받아들이고,&amp;nbsp;죽기&amp;nbsp;전에&amp;nbsp;돌아봤을&amp;nbsp;떄&amp;nbsp;나의&amp;nbsp;인생이&amp;nbsp;무엇을&amp;nbsp;가득&amp;nbsp;채워졌는지&amp;nbsp;고민해볼&amp;nbsp;필요가&amp;nbsp;있을&amp;nbsp;것&amp;nbsp;같다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>서평</category>
      <category>스토너</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/643</guid>
      <comments>https://insengnewbie.tistory.com/643#entry643comment</comments>
      <pubDate>Tue, 31 Mar 2026 11:44:05 +0900</pubDate>
    </item>
    <item>
      <title>『우리, 프로그래머들』 리뷰 &amp;mdash; 우리는 누구이며, 왜 여기에 있는가</title>
      <link>https://insengnewbie.tistory.com/642</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf1frv/dJMcabXBO8l/CH6k1BsbAZbCIpnkzBRbv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf1frv/dJMcabXBO8l/CH6k1BsbAZbCIpnkzBRbv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf1frv/dJMcabXBO8l/CH6k1BsbAZbCIpnkzBRbv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf1frv%2FdJMcabXBO8l%2FCH6k1BsbAZbCIpnkzBRbv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;714&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길벗 출판사의 개발자 도서 리뷰어로 선정되어 『우리, 프로그래머들』을 읽게 됐다. 책을 펼치기 전엔 &amp;ldquo;역사책 느낌일까?&amp;rdquo; 정도로 생각했는데, 다 읽고 나서는 오히려 &lt;b&gt;&amp;ldquo;정체성에 대한 책&amp;rdquo;&lt;/b&gt;으로 남았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우리는 누구인가? 우리는 왜 존재하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책의 첫 인상은 질문에서 시작됐다.&lt;br /&gt;&amp;ldquo;우리는 누구인가? 우리는 왜 존재하는가?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 개발 실무는 프레임워크, 라이브러리, 인프라가 너무 촘촘하게 갖춰져 있어서, 개발자라는 직업을 &lt;b&gt;&amp;lsquo;기술 스택을 잘 다루는 사람&amp;rsquo;&lt;/b&gt;으로만 정의하기 쉬운데&amp;hellip; 이 책은 그 정의를 가만히 흔들어 놓는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머는 언제나 도구보다 먼저 &amp;lsquo;사고방식&amp;rsquo;을 가진 사람이었다는 걸, 역사를 따라가며 보여준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로버트 C. 마틴이 말하는 &amp;ldquo;프로그래머의 역사&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『클린 코드』로 유명한 로버트 C. 마틴(엉클 밥)의 신작이라는 점만으로도 기대가 컸는데, 이 책은 &amp;ldquo;좋은 코드&amp;rdquo;가 아니라 &lt;b&gt;&amp;ldquo;프로그래머라는 존재의 형성 과정&amp;rdquo;&lt;/b&gt;을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍의 시작부터 지금까지, 프로그래밍이 어떻게 진화해왔는지 개략적인 역사를 풀어준다. 천공카드, 진공관, 에니악 같은 까마득하게 먼 시대&amp;mdash;심지어 지금 우리가 생각하는 의미의 &amp;ldquo;프로그래밍&amp;rdquo;이라고 부르기도 애매했던 시절부터 시작해, 현대의 컴퓨팅으로 이어지는 흐름을 짚어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽으며 가장 인상 깊었던 건, 기술의 발전이 단순히 속도와 용량의 문제가 아니었다는 것이다.&lt;br /&gt;예전에는 상상하기 어려운 물리적 제약 속에서 컴퓨터를 굴렸고, 지금은 손톱만한 메모리 장치로도 당시와 비교할 수 없는 계산과 저장을 한다. (읽는 내내 &amp;ldquo;지금의 당연함이 얼마나 많은 축적 위에 있나&amp;rdquo;를 계속 떠올리게 됐다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과거의 프로그래머와 우리는 다르지만, 본질은 같다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 프로그래머와 현재의 프로그래머가 하는 일은 세세하게 보면 너무 다르다.&lt;br /&gt;그때는 하드웨어와 붙어 싸웠고, 지금은 추상화된 계층 위에서 문제를 풀고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데도 책을 읽는 동안 계속 들었던 생각은 이거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 본질적으로 같다.&lt;br /&gt;&amp;bull; 문제를 정의하고&lt;br /&gt;&amp;bull; 제약을 이해하고&lt;br /&gt;&amp;bull; 더 나은 방식으로 풀어내려 하고&lt;br /&gt;&amp;bull; 누군가가 반복하지 않도록 지식을 남긴다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 시대가 바뀌어도 변하지 않는 프로그래머의 핵심이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 시대에 더 필요한 &amp;ldquo;프로그래머의 정신&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 AI가 코드를 뽑아주고, 자동완성이 문장을 완성하고, 프레임워크가 대부분의 결정을 대신해준다.&lt;br /&gt;편해진 만큼, 한편으로는 개발자의 정체성이 흐려지기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 그 지점에서 의미가 컸다.&lt;br /&gt;AI 시대에 잊혀가기 쉬운 프로그래머의 정신&amp;mdash;스스로 사고하고, 맥락을 이해하고, 책임 있게 시스템을 만드는 태도&amp;mdash;를 다시 깨우는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 도구가 아무리 강력해져도, &amp;ldquo;무엇을 만들지 / 왜 만들지 / 어떤 결과를 초래할지&amp;rdquo;는 사람이 결정한다.&lt;br /&gt;이 책은 그 &amp;ldquo;사람&amp;rdquo;의 계보를 보여준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴퓨터라는 장치가 인류 역사 속에서 만들어진 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대의 컴퓨터가 갑자기 뚝 떨어진 기술이 아니라, 인류 역사 속에서 차근차근 축적된 결과물이라는 흐름이 흥미로웠다.&lt;br /&gt;프로그래머가 어떻게 시작되었고 지금까지 어떻게 달려왔는지, 그리고 앞으로 어떻게 나아갈 수 있을지를 한 호흡으로 연결해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역사를 읽는다는 건 단순한 회고가 아니라, 현재의 위치를 좌표로 찍는 일이라는 생각이 들었다.&lt;br /&gt;&amp;ldquo;나는 지금 어떤 계보 위에 서 있지?&amp;rdquo; 같은 질문이 자연스럽게 따라왔다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;특히 그레이스 호퍼를 더 알게 된 책&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 그레이스 호퍼 이야기가 특히 기억에 남는다.&lt;br /&gt;이름만 어렴풋이 알고 있었던 인물을, 좀 더 입체적으로 바라보게 됐다.&lt;br /&gt;&amp;ldquo;기술의 발전&amp;rdquo; 뒤에는 언제나 &amp;ldquo;사람의 확신과 선택&amp;rdquo;이 있었고, 그 선택들이 모여 지금의 표준과 문화가 됐다는 걸 다시 체감했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 &amp;mdash; 기술서가 아니라, &amp;lsquo;우리에 대한 책&amp;rsquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;『우리, 프로그래머들』은 어떤 기술을 알려주는 책은 아니다.&lt;br /&gt;그보다는 개발자로 살아가며 가끔 잊어버리는 질문&amp;mdash;&amp;ldquo;우리는 누구이고, 왜 존재하는가&amp;rdquo;&amp;mdash;를 다시 꺼내준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기술이 빠르게 변할수록&lt;/li&gt;
&lt;li&gt;도구가 강력해질수록&lt;/li&gt;
&lt;li&gt;&quot;개발자다움&quot;이 희미해진다고 느낄수록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책이 더 잘 읽힐 것 같다.&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>협찬</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/642</guid>
      <comments>https://insengnewbie.tistory.com/642#entry642comment</comments>
      <pubDate>Mon, 2 Mar 2026 03:36:31 +0900</pubDate>
    </item>
    <item>
      <title>AI를 쓰면서도 엔지니어로 남는 법</title>
      <link>https://insengnewbie.tistory.com/640</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI가 편해질수록 비워지는 느낌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품을 개발하면서 AI를 더 자주 쓰게 될수록, 어딘가 제 머리가 비워지는 느낌을 받았습니다. 얼마 전에는 AI가 작성한 코드에서 버그가 발생했습니다. 처음에는 직접 디버깅해 보려 했지만, 막상 코드를 따라가며 맥락을 복원하는 일이 생각보다 쉽지 않았습니다. 결국 저는 최근에 출시된 &lt;code&gt;gpt-5.3-codex&lt;/code&gt;에게 수정을 맡겼고, 문제는 빠르게 해결되었습니다. 그런데 그 순간부터 묘한 기시감이 남았습니다. &amp;ldquo;이렇게까지 AI에게 위임해도 괜찮은 걸까?&amp;rdquo;, &amp;ldquo;개발은 빨라졌는데 도메인 이해는 얕아진 채로 다음 이슈로 넘어가는 건 아닐까?&amp;rdquo;, &amp;ldquo;AI가 계속 발전한다면, 나는 프롬프트를 쓰는 것 말고 무엇을 할 수 있을까?&amp;rdquo; 같은 질문들이 몇 주 동안 머릿속을 맴돌았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 AI를 쓰기 시작했을 때만 해도, 저는 최소한의 절차를 지키려 했습니다. 계획 &amp;rarr; 구현 &amp;rarr; 디버깅 &amp;rarr; 리팩토링의 순서로 과정을 구조화해, 적어도 &amp;lsquo;이해한 상태&amp;rsquo;에서 다음 단계로 넘어가고 싶었기 때문입니다. 그러나 AI가 제 의도를 점점 더 잘 맞히기 시작하자, 그 과정은 조금씩 생략되기 시작했습니다. 어느새 저는 &amp;ldquo;~해줘&amp;rdquo;라는 한 문장으로 결과물을 받아들고, 그 결과를 충분히 이해하지 못한 채 넘어가는 일이 잦아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 불안의 원인을 처음에는 두 가지로 추측했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;새로운 방식을 처음 접했을 때 낯선 것에서 느껴지는 불편함&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AI에게 대체당해서 미래에 어떻게 해야 할지 모르겠다는 두려움&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 제 경우에는 둘 다 핵심이 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;불안의 이름: 인지 부채&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot;&gt;Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing T...&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770651010370&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing Task &amp;ndash; MIT Media Lab&quot; data-og-description=&quot;This study explores the neural and behavioral consequences of LLM-assisted essay writing. Participants were divided into three groups: LLM, Search Engine, and &amp;hellip;&quot; data-og-host=&quot;www.media.mit.edu&quot; data-og-source-url=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot; data-og-url=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing Task &amp;ndash; MIT Media Lab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This study explores the neural and behavioral consequences of LLM-assisted essay writing. Participants were divided into three groups: LLM, Search Engine, and &amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.media.mit.edu&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 논문은 생성형 AI가 즉각적인 편의성을 제공하는 대신, 장기적으로는 사용자의 사고 과정에 &amp;lsquo;인지적 부채(cognitive debt)&amp;rsquo;가 쌓일 수 있다는 가능성을 제기합니다. 저는 이 개념을 &amp;ldquo;AI가 일을 대신해 주는 만큼, 내가 직접 문제를 분해하고 가설을 세우고 검증하는 과정이 생략되는 상태&amp;rdquo;로 받아들였습니다. 단기적으로는 산출물이 좋아지고 속도가 빨라지지만, 장기적으로는 제 머릿속에 재사용 가능한 청크(패턴)가 덜 남을 수 있다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 연구는 에세이 글쓰기를 다루지만, 제게는 개발에서도 비슷한 감각이 있었습니다. 제게 남았던 기시감도 결국 &amp;ldquo;편해졌는데, 내 안에 무엇이 축적되고 있지?&amp;rdquo;라는 질문이었습니다. 기능은 더 빨리 완성되는데, 제가 &amp;ldquo;왜 이렇게 만들었는지&amp;rdquo;를 설명할 언어가 남지 않는 느낌 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때부터 저는 불안을 &amp;ldquo;AI에게 대체될지도 모른다&amp;rdquo;가 아니라 &amp;ldquo;소프트웨어 엔지니어로서의 내가 사라질지도 모른다&amp;rdquo;로 다시 정의하게 되었습니다. 그리고 이 정의는, 제가 지난 2년 동안 서로 다른 두 종류의 즐거움을 동시에 맛보고 있었기 때문에 더 선명해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 가지 즐거움, 두 가지 자아&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 지난 2년 동안 개발자로서 두 종류의 즐거움을 경험했습니다. 하나는 &amp;ldquo;프로덕트 엔지니어&amp;rdquo;로서의 즐거움입니다. 사용자가 만족할 만한 제품을 만들어 세상에 내보내고, 지표를 확인하며 내일은 무엇을 더 만들어볼지 고민하는 일에서 에너지를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 하나는 &amp;ldquo;소프트웨어 엔지니어&amp;rdquo;로서의 즐거움입니다. 지속 가능하고 유지보수하기 쉬운 구조를 만들고, 성능 최적화로 병목을 제거하고, 새로운 기술을 이해해 적용하는 과정에서 만족을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 자아 중 무엇이 진짜 저일까요. 저는 『무의식은 어떻게 뇌를 설계하는가』를 읽으며, &amp;lsquo;유일하고 진정한 나&amp;rsquo;가 있다기보다 순간순간의 선택과 습관이 모여 지금의 내가 된다는 관점이 인상 깊었습니다. 그렇다면 프로덕트 엔지니어로서의 저도, 소프트웨어 엔지니어로서의 저도 모두 제 일부입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 &lt;b&gt;예측할 수도 없는 미래에 맞춰 &amp;ldquo;소프트웨어 엔지니어&amp;rdquo;라는 저의 일면을 스스로 없애고 싶지 않았던 것&lt;/b&gt;입니다. AI를 잘 쓰는 사람이 되고 싶지만, 동시에 엔지니어로서의 축적도 포기하고 싶지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미래는 예측 불가능하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크드인이나 주변의 대화를 듣다 보면 &amp;ldquo;언젠가는 AI가 개발자를 대체할 것이다&amp;rdquo;, &amp;ldquo;소수의 관리자/시니어만 남을 것이다&amp;rdquo;, &amp;ldquo;지금도 AI만으로 1인 기업을 만든다&amp;rdquo; 같은 말이 자주 오갑니다. 저는 이런 예측이 맞느냐 틀리냐를 따지고 싶지는 않습니다. 다만 한 가지는 분명하다고 생각합니다. 미래는, 우리가 바라는 만큼 예측 가능하지 않다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래가 완벽히 예측 가능했다면, 우리가 지금 투자하는 학습과 시행착오는 얼마나 허무했을까요. 그러나 현실은 늘 그 반대였습니다. 기술은 예상치 못한 방향으로 바뀌고, 직무의 경계도 바뀌고, &amp;lsquo;필요한 역량&amp;rsquo;의 이름도 자주 바뀝니다. 그래서 저는 예측 게임 대신, 지금의 제 습관과 작업 방식을 설계하는 쪽을 선택하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공존을 위한 세 가지 프레임&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 찾고 싶은 길은 단순합니다. AI 트렌드에 올라타되, 프로덕트 엔지니어로서의 속도와 소프트웨어 엔지니어로서의 축적이 같은 방향을 보게 만드는 것입니다. 이를 위해 저는 아래 세 가지 프레임을 잡아보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 생산과 축적을 분리해서 생각하기&lt;br /&gt;AI는 산출물을 빠르게 만들어 주지만, 제 지식까지 자동으로 만들어 주지는 않습니다. 그래서 저는 개발을 끝내는 것과, 이해를 남기는 것을 서로 다른 작업으로 취급하려 합니다. 같은 PR이라도 &amp;ldquo;완성&amp;rdquo;과 &amp;ldquo;축적&amp;rdquo;은 체크리스트가 달라야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 구현은 위임해도, 이해는 외주화하지 않기&lt;br /&gt;버그를 고치는 일까지 AI에게 맡길 수 있습니다. 하지만 그 순간에 제가 놓치기 쉬운 것은 &amp;ldquo;왜 이 수정이 맞는가&amp;rdquo;입니다. 저는 앞으로 코드가 통과해야 하는 불변식(어떤 상황에서도 지켜야 하는 조건)과 실패 케이스를 먼저 적고, AI의 수정이 그것을 만족하는지 제 언어로 다시 확인하려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) AI를 &amp;ldquo;제가 읽기 쉬운 코드&amp;rdquo; 쪽으로 정렬하기&lt;br /&gt;좋은 코드, 혹은 읽기 쉬운 코드는 결국 인간의 뇌가 부담 없이 처리할 수 있는 형태를 가집니다. 아래 글은 그 &amp;ldquo;읽기 쉬움&amp;rdquo;의 감각을 작업 기억, 청크, 예측 가능성 같은 관점으로 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot;&gt;우리는 왜 어떤 코드를 읽기 쉽다고 느낄까&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770651033009&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;우리는 왜 어떤 코드를 읽기 쉽다고 느낄까&quot; data-og-description=&quot;이전에 좋은 코드란 무엇일까? - 가독성이란 허상에 대하여라는 글에서, &amp;ldquo;좋은 코드 = 가독성이 좋은 코드&amp;rdquo;라는 공식이 얼마나 주관적이고 맥락 의존적인지에 대해 이야기한 적이 있다. 물론 &quot; data-og-host=&quot;evan-moon.github.io&quot; data-og-source-url=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot; data-og-url=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPLJTl/dJMb86OXCcf/NGP2D5257fuA0192ouA6i0/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320,https://scrap.kakaocdn.net/dn/cAkEMj/dJMb86nTknX/DikZZf7tcQFaBSxjD8jhLK/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPLJTl/dJMb86OXCcf/NGP2D5257fuA0192ouA6i0/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320,https://scrap.kakaocdn.net/dn/cAkEMj/dJMb86nTknX/DikZZf7tcQFaBSxjD8jhLK/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;우리는 왜 어떤 코드를 읽기 쉽다고 느낄까&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전에 좋은 코드란 무엇일까? - 가독성이란 허상에 대하여라는 글에서, &amp;ldquo;좋은 코드 = 가독성이 좋은 코드&amp;rdquo;라는 공식이 얼마나 주관적이고 맥락 의존적인지에 대해 이야기한 적이 있다. 물론&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;evan-moon.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;읽기 쉬움&amp;rdquo;이 줄여주는 비용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 기억은 제한적입니다. 한 번에 머릿속에 올려둘 수 있는 정보가 제한되어 있기 때문에, 코드가 너무 많은 상태/분기/개념을 동시에 요구하면 이해 비용이 급격히 올라갑니다.&lt;/li&gt;
&lt;li&gt;뇌는 반복되는 패턴을 &amp;ldquo;청크&amp;rdquo;로 묶어 빠르게 처리합니다. 익숙한 구조와 관용적 네이밍은 &amp;lsquo;새로 해석해야 하는 양&amp;rsquo;을 줄이고, 이미 있는 청크를 재사용하게 만듭니다.&lt;/li&gt;
&lt;li&gt;우리는 다음 전개를 계속 예측하면서 읽습니다. 예측 가능한 코드(일관된 흐름, 예상 가능한 부작용, 표준적인 에러 처리)는 예측 오차를 줄여 주고, 그만큼 사고를 본질적인 문제에 쓸 여력을 남깁니다.&lt;/li&gt;
&lt;li&gt;시각적 구조와 논리적 구조가 어긋나면(예: 들여쓰기/중첩이 의미를 숨기거나, 이름이 역할을 가리지 못하거나, 책임이 섞이면) 작업 기억을 더 많이 소모하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관점에서 보면 &amp;ldquo;읽기 쉬움&amp;rdquo;은 취향의 문제가 아니라, 제 뇌가 지불해야 하는 비용의 문제에 가깝습니다. 읽기 쉬운 코드는 단지 빨리 읽히는 것이 아니라, 더 적은 비용으로 더 많은 맥락을 남기게 해줍니다. 같은 코드를 다시 만나도 빠르게 복원할 수 있고, 비슷한 문제를 다시 풀 때도 내부의 청크를 재사용할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 관점을 AI 사용 방식에 적용해 보고 싶습니다. AI가 작성한 코드가 제 머릿속에 이미 있는 패턴과 잘 맞고, 이름과 구조가 예측 가능하며, 시각적 구조가 논리적 구조와 일치할수록, 저는 더 적은 비용으로 더 많은 맥락을 흡수할 수 있습니다. 결국 목표는 &lt;b&gt;&amp;ldquo;AI가 소프트웨어 엔지니어로서의 제가 제 철학에 맞게 썼을 법한 코드&amp;rdquo;&lt;/b&gt;를 쓰게 만드는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이제 막 2년차를 넘긴 주니어 개발자이고, &amp;ldquo;설계 철학&amp;rdquo;이라고 부를 만한 것이 아직 선명하지 않습니다. 그럼에도 저는 프로덕트 엔지니어로서의 속도와, 소프트웨어 엔지니어로서의 축적을 둘 다 놓치고 싶지 않습니다. 아마 이 글은 그 두 자아를 동시에 지키기 위한 첫 번째 기록일 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.media.mit.edu/publications/your-brain-on-chatgpt/&quot;&gt;Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing T...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://news.hada.io/topic?id=26405&quot;&gt;깊이 생각하던 시절이 그리워요 | GeekNews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://evan-moon.github.io/2026/01/30/developer-intuition-readable-code-and-neuroscience/&quot;&gt;우리는 왜 어떤 코드를 읽기 쉽다고 느낄까&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000214761920&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000214761920&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1770651091370&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;무의식은 어떻게 나를 설계하는가 | 데이비드 이글먼 - 교보문고&quot; data-og-description=&quot;무의식은 어떻게 나를 설계하는가 | 뇌과학계의 칼 세이건, 데이비드 이글먼 연구의 첫걸음 우리가 뇌에 대해 궁금해하는 질문들에 관해 현대 뇌과학이 내놓은 해답.오늘 했던 행동이 정말 내가&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000214761920&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000214761920&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXwCoj/dJMb86OXCcp/GbpeGPYpVdY4zVimftOpHK/img.jpg?width=458&amp;amp;height=680&amp;amp;face=0_0_458_680,https://scrap.kakaocdn.net/dn/y2Ljm/dJMb85WO390/sHuxpAfXzjATNkEB2AkV0K/img.jpg?width=458&amp;amp;height=680&amp;amp;face=0_0_458_680&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000214761920&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000214761920&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXwCoj/dJMb86OXCcp/GbpeGPYpVdY4zVimftOpHK/img.jpg?width=458&amp;amp;height=680&amp;amp;face=0_0_458_680,https://scrap.kakaocdn.net/dn/y2Ljm/dJMb85WO390/sHuxpAfXzjATNkEB2AkV0K/img.jpg?width=458&amp;amp;height=680&amp;amp;face=0_0_458_680');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;무의식은 어떻게 나를 설계하는가 | 데이비드 이글먼 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;무의식은 어떻게 나를 설계하는가 | 뇌과학계의 칼 세이건, 데이비드 이글먼 연구의 첫걸음 우리가 뇌에 대해 궁금해하는 질문들에 관해 현대 뇌과학이 내놓은 해답.오늘 했던 행동이 정말 내가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/640</guid>
      <comments>https://insengnewbie.tistory.com/640#entry640comment</comments>
      <pubDate>Tue, 10 Feb 2026 00:29:42 +0900</pubDate>
    </item>
    <item>
      <title>생각정리독서법 후기</title>
      <link>https://insengnewbie.tistory.com/639</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;책을 읽는 시간은 늘었는데, 남는 건 희미했다. 출퇴근 10~20분, 틈틈이 책을 펼치면서도 &amp;ldquo;이 책이 내 생각을 얼마나 바꿨지?&amp;rdquo;라는 질문에 선뜻 답하지 못했다. 그런 시점에 《생각정리독서법》을 읽었다. 이 책은 &amp;ldquo;더 많이 읽기&amp;rdquo;보다 &amp;ldquo;더 잘 남기기&amp;rdquo;에 초점을 맞춘다. 나에게는 바로 그 지점이 필요했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 요약 (6가지)&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #e1e4e8; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;독서는 특별한 행사가 아니라, 작게라도 자주 꾸준히 하는 습관이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;독서리스트와 기록은 기억을 돕고, 생각을 논리적으로 정리하게 만든다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AI 시대일수록 필요한 것은 사고력&amp;middot;창의력이며, 그 힘은 책을 천천히 읽는 과정에서 길러진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;독서는 사고의 &amp;ldquo;깊이&amp;rdquo;와 &amp;ldquo;넓이&amp;rdquo;를 키운다. 깊이는 한 주제를 끝까지 파고드는 힘, 넓이는 여러 관점으로 이해하는 힘이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좋은 책 고르기는 목적을 분명히 하는 데서 시작한다. 목적이 선명해지면 RAS가 작동해 필요한 책이 더 잘 보인다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 파악을 위한 흐름이 있다: 표지 &amp;rarr; 저자 소개 &amp;rarr; 서문 &amp;rarr; 목차 &amp;rarr; 내용. 이 예열 과정이 이후 이해와 기억을 바꾼다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요약&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;책이 말하는 핵심은 단순하다. 독서는 특별한 이벤트가 아니라 작은 습관이며, 기록은 그 습관을 성장으로 바꿔준다. AI 시대에 필요한 것은 사고력/창의력이고, 그 힘은 책을 한 장씩 읽으며 길러진다. 사고가 깊어진다는 것은 한 주제를 끝까지 파고드는 능력이고, 넓어진다는 것은 여러 관점으로 바라보는 능력이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좋은 책 선택과 이해를 위해선 &amp;ldquo;목적&amp;rdquo;이 선명해야 한다. 목적이 있을 때 RAS가 작동해 필요한 책이 눈에 들어온다. 읽기 전에는 표지 &amp;rarr; 저자 소개 &amp;rarr; 서문 &amp;rarr; 목차 &amp;rarr; 내용의 흐름으로 예열하면 이해와 기억이 더 쉬워진다. 요약은 단순 축약이 아니라 저자의 의도를 정확히 파악해 재구성하는 과정이며, 핵심을 찾는 의지가 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;느낀 점&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 마음에 남은 문장은 &lt;i&gt;&amp;ldquo;책에는 머리로 기억에 남는 것이 있고, 기억하려고 애쓰지 않아도 가슴에 남는 문장이 있다.&amp;rdquo;&lt;/i&gt;였다. 그런데 솔직히 나는 이전까지 가슴에 남는 문장을 많이 건져내지 못하고 있었다. 그래서 &amp;ldquo;그래서 핵심이 뭐지?&amp;rdquo;라는 질문을 스스로에게 자주 던져야겠다고 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 하나는 &amp;ldquo;당신은 주변 5명의 평균이다.&amp;rdquo;라는 문장. 내가 가까이 두는 책들이 결국 나를 만든다는 사실이 선명해졌다. 읽는 권수를 늘리는 것만으로는 부족하고, 기억과 표현으로 이어지는 독서가 필요하다는 걸 인정하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #e1e4e8; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;읽기 전에 목적 한 줄 쓰기: &amp;ldquo;왜 이 책을 읽는가?&amp;rdquo;를 메모해 RAS를 깨우기.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5단계 예열: 표지 &amp;rarr; 저자 &amp;rarr; 서문 &amp;rarr; 목차 &amp;rarr; 내용으로 큰 틀 먼저 잡기.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;옵시디언 메모를 사용해서 책을 읽고 기록하기.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마음에 남는 문장은 따로 모아 말이나 글에서 인용하기.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC3azH/dJMcaiBTzmd/3gsMHM8PJcSCkrHKPOvZl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC3azH/dJMcaiBTzmd/3gsMHM8PJcSCkrHKPOvZl0/img.png&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;1132&quot; data-is-animation=&quot;false&quot; width=&quot;689&quot; height=&quot;840&quot; data-widthpercent=&quot;54.36&quot; style=&quot;width: 53.7275%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC3azH/dJMcaiBTzmd/3gsMHM8PJcSCkrHKPOvZl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC3azH%2FdJMcaiBTzmd%2F3gsMHM8PJcSCkrHKPOvZl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;929&quot; height=&quot;1132&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JNwGg/dJMcadm2M4u/73tuKuzi4LnudYkPEw0E20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JNwGg/dJMcadm2M4u/73tuKuzi4LnudYkPEw0E20/img.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;1341&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.1097%;&quot; data-widthpercent=&quot;45.64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JNwGg/dJMcadm2M4u/73tuKuzi4LnudYkPEw0E20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJNwGg%2FdJMcadm2M4u%2F73tuKuzi4LnudYkPEw0E20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;1341&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;옵시디언 노트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #e1e4e8; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;《생각정리독서법》은 독서를 &amp;lsquo;축적&amp;rsquo;이 아니라 &amp;lsquo;전환&amp;rsquo;으로 바꾸는 방법을 알려준다. &amp;ldquo;책을 제대로 읽는다는 것&amp;rdquo;은 결국 저자의 핵심을 붙잡고, 내 언어로 다시 정리하는 것이라는 메시지가 남는다. 2026년에는 더 많이 읽기보다, 더 정확히 남기기를 목표로 삼아보고 싶다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>독서</category>
      <category>생각정리독서법</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/639</guid>
      <comments>https://insengnewbie.tistory.com/639#entry639comment</comments>
      <pubDate>Sat, 27 Dec 2025 01:08:16 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자가 노션 하나로 개발 효율 3배 높인 후기</title>
      <link>https://insengnewbie.tistory.com/638</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQvCw/dJMcaaYbmGQ/sO1QfBvBop2IMtkI4PZl4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQvCw/dJMcaaYbmGQ/sO1QfBvBop2IMtkI4PZl4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQvCw/dJMcaaYbmGQ/sO1QfBvBop2IMtkI4PZl4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQvCw%2FdJMcaaYbmGQ%2FsO1QfBvBop2IMtkI4PZl4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;300&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvqdc4/dJMcafd7Cpc/AgkcjJso2tPrKv3ofAzRbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvqdc4/dJMcafd7Cpc/AgkcjJso2tPrKv3ofAzRbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvqdc4/dJMcafd7Cpc/AgkcjJso2tPrKv3ofAzRbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvqdc4%2FdJMcafd7Cpc%2FAgkcjJso2tPrKv3ofAzRbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;350&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/2.5%ED%86%A4-%EC%A0%81%EC%9E%AC%ED%95%A8-%ED%81%AC%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;센디 블로그 - 2.5톤 트럭에 얼마나 실을 수 있을까요?&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766332132143&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;센디 블로그 - 2.5톤 트럭에 얼마나 실을 수 있을지, 숫자로 정리해봤어요!&quot; data-og-description=&quot;납품 업무를 하다 보면 2.5톤 트럭이면 충분한지, 아니면 부족한지 매번 계산이 애매할 때가 많아요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/blog/2.5%ED%86%A4-%EC%A0%81%EC%9E%AC%ED%95%A8-%ED%81%AC%EA%B8%B0&quot; data-og-url=&quot;https://sendy.ai/blog/2.5%ED%86%A4-%EC%A0%81%EC%9E%AC%ED%95%A8-%ED%81%AC%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iJHK3/hyZP4nn9I9/HhgXgDfZjR54CJAv0xN6vk/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/3feaU/hyZP8pOehw/Wamx4ZFklm9vlMkc7MWJK0/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/dAWkK8/hyZPZ0HZfm/gbybKiXIunIdFMLeQ0KXwk/img.png?width=960&amp;amp;height=576&amp;amp;face=0_0_960_576&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/2.5%ED%86%A4-%EC%A0%81%EC%9E%AC%ED%95%A8-%ED%81%AC%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/blog/2.5%ED%86%A4-%EC%A0%81%EC%9E%AC%ED%95%A8-%ED%81%AC%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iJHK3/hyZP4nn9I9/HhgXgDfZjR54CJAv0xN6vk/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/3feaU/hyZP8pOehw/Wamx4ZFklm9vlMkc7MWJK0/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/dAWkK8/hyZPZ0HZfm/gbybKiXIunIdFMLeQ0KXwk/img.png?width=960&amp;amp;height=576&amp;amp;face=0_0_960_576');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디 블로그 - 2.5톤 트럭에 얼마나 실을 수 있을지, 숫자로 정리해봤어요!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;납품 업무를 하다 보면 2.5톤 트럭이면 충분한지, 아니면 부족한지 매번 계산이 애매할 때가 많아요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 전. Cmd+C, Cmd+V. 3일 동안 내가 한 일은 이게 전부였다. '이것도 개발인가?' 하는 생각이 들 때쯤, '언제 끝나지...' 하는 한숨이 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM이 작성한 노션 문서를 열고, 디자이너가 만든 피그마를 열고, 텍스트를 복사해서 코드에 붙여넣는다. 다음 섹션으로 넘어가서 또 복사, 붙여넣기. 가이드 페이지 하나를 완성하는 데 이런 작업만 3일이 걸렸다. QA 단계에서는 한 글자, 한 글자 수정하느라 더 힘들었다. 랜딩을 다루는 프론트엔드 개발자라면 누구나 한 번쯤 겪어봤을, 그 지루한 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 1년 만에, 또다시 콘텐츠 페이지를 만들어야 하는 상황이 왔다. 이번에는 &lt;b&gt;센디 블로그&lt;/b&gt; 페이지였다. 순간 3일간의 복붙 지옥이 떠올랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니, 이번에는 절대 안 된다. 같은 작업을 반복하기엔 시간이 너무 아까웠다. 이 과정 자체를 바꿀 방법을 찾아야 했다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;글 작성은 너무 귀찮았다!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이런 경험이 처음은 아니었다. 예전에 가이드 페이지를 만들 때도 똑같은 과정을 거쳤다. PM이 작성한 노션 문서를 보면서, 디자이너가 만들어준 컴포넌트에 텍스트를 하나하나 복사 붙여넣기 했다. 3일 내내 텍스트만 복사하고 붙여넣다가, QA 때는 한 글자씩 수정하느라 정말 지쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 우리 팀의 콘텐츠 제작 워크플로우는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;PM이 글을 작성한다&lt;/li&gt;
&lt;li&gt;디자이너가 디자인에 맞게 콘텐츠 UI를 그린다&lt;/li&gt;
&lt;li&gt;프론트엔드 개발자가 피그마를 보면서 콘텐츠를 렌더링한다&lt;/li&gt;
&lt;li&gt;QA하면서 한 글자씩 수정한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 3번과 4번이었다. 개발자가 피그마와 코드 사이를 오가며 텍스트를 옮기고, 오탈자나 수정 사항이 생기면 또다시 코드를 열어서 한 글자씩 고쳐야 했다. 단순하고 기계적인 작업인데, 개발자의 시간이 여기에 묶여있다는 게 비효율적이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화를 생각하지 않은 건 아니었다. Cursor IDE나 Antigravity IDE에 Notion MCP를 연결하면 자동화할 수 있었다. 하지만 내가 원하는 방향은 아니었다. MCP는 개발자가 입력하는 프롬프트에 따라 결과가 달라진다. &quot;간결하게 만들어줘&quot;라고 하면 A 스타일로, &quot;자세하게 만들어줘&quot;라고 하면 B 스타일로 나온다. 다른 개발자가 작업하면 완전히 다른 코드가 나올 수 있다는 얘기다. 코드 리뷰나 유지보수 때 일관성이 깨지는 건 피하고 싶었다. 어떤 개발자가 유지보수하게 되더라도 일관되게 동작 가능한 방식을 원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 고민만 하고 있었다. 더 나은 방법이 있을 것 같은데, 뭔지 떠오르지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;뜻밖의 해결방법, 노션 콘텐츠 자동화!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 고민만 하던 중, 백엔드 개발자 주드가 흥미로운 아티클을 공유했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/article/only-3-users-product&quot;&gt;https://toss.tech/article/only-3-users-product&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766331809935&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;사용자 3명인 제품, 만들어야 할까?&quot; data-og-description=&quot;사용자가 3명 뿐인 제품, 쓰는 사람이 적으면 개선을 하지 말아야 할까요? 토스 콘텐츠 시스템(TCS)을 만들면서, 최소한의 비용으로 최대한의 효율을 내기 위해 고민했던 지점들을 소개해드려요.&quot; data-og-host=&quot;toss.tech&quot; data-og-source-url=&quot;https://toss.tech/article/only-3-users-product&quot; data-og-url=&quot;https://toss.tech/article/only-3-users-product&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OIoFI/hyZP8wAaFQ/5kLnKjkhW4hqWny5gwON6K/img.png?width=1790&amp;amp;height=896&amp;amp;face=0_0_1790_896,https://scrap.kakaocdn.net/dn/n57ua/hyZPMtnKwG/GxkJeqJqtlLHKkJOAFUmmk/img.png?width=1790&amp;amp;height=896&amp;amp;face=0_0_1790_896,https://scrap.kakaocdn.net/dn/eY9Bl/hyZP1YwExa/zb36OeLC2fIMKeQ1sEAeS0/img.png?width=1024&amp;amp;height=1019&amp;amp;face=0_0_1024_1019&quot;&gt;&lt;a href=&quot;https://toss.tech/article/only-3-users-product&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.tech/article/only-3-users-product&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OIoFI/hyZP8wAaFQ/5kLnKjkhW4hqWny5gwON6K/img.png?width=1790&amp;amp;height=896&amp;amp;face=0_0_1790_896,https://scrap.kakaocdn.net/dn/n57ua/hyZPMtnKwG/GxkJeqJqtlLHKkJOAFUmmk/img.png?width=1790&amp;amp;height=896&amp;amp;face=0_0_1790_896,https://scrap.kakaocdn.net/dn/eY9Bl/hyZP1YwExa/zb36OeLC2fIMKeQ1sEAeS0/img.png?width=1024&amp;amp;height=1019&amp;amp;face=0_0_1024_1019');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;사용자 3명인 제품, 만들어야 할까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 3명 뿐인 제품, 쓰는 사람이 적으면 개선을 하지 말아야 할까요? 토스 콘텐츠 시스템(TCS)을 만들면서, 최소한의 비용으로 최대한의 효율을 내기 위해 고민했던 지점들을 소개해드려요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;toss.tech&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스 팀의 &quot;단 3명의 유저를 위한 프로덕트 개발기&quot;라는 글이었다. 글을 읽는 순간, &quot;우리만의 고민이 아니었구나&quot;라는 생각이 들었다. 알고 보니 3년 전 토스에서도 똑같은 고민을 했고, 해결 방법까지 찾아놨다. 노션에서 작성한 글을 바로 가져와서 렌더링하는 방식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아이디어를 보자마자 &quot;이거다!&quot;라는 확신이 들었다. 우리도 바로 적용할 수 있을 것 같았다. 한번 구현해두면, PM이 노션에서 글을 작성하기만 하면 자동으로 페이지에 반영된다. 개발자는 초기 설정만 하면 되고, 그 다음부터는 글 작성만 남는다. PM이 어떻게 렌더링되는지 실시간으로 보면서 작업할 수 있고, 오탈자도 직접 수정할 수 있다. 기존 4단계 워크플로우의 3번과 4번이 통째로 사라지는 셈이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 작업에 착수했고, react-notion-x라는 라이브러리를 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/NotionX/react-notion-x&quot;&gt;https://github.com/NotionX/react-notion-x&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766331816536&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - NotionX/react-notion-x: Fast and accurate React renderer for Notion. TS batteries included. ⚡️&quot; data-og-description=&quot;Fast and accurate React renderer for Notion. TS batteries included. ⚡️ - NotionX/react-notion-x&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/NotionX/react-notion-x&quot; data-og-url=&quot;https://github.com/NotionX/react-notion-x&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RQ3vK/hyZP9vtyd8/0i3emv8kaELU3H9sTQEJ40/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bBmisV/hyZODkvN9J/8RSFOVs2Km32qaqk58ec6K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/NotionX/react-notion-x&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/NotionX/react-notion-x&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RQ3vK/hyZP9vtyd8/0i3emv8kaELU3H9sTQEJ40/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bBmisV/hyZODkvN9J/8RSFOVs2Km32qaqk58ec6K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - NotionX/react-notion-x: Fast and accurate React renderer for Notion. TS batteries included. ⚡️&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fast and accurate React renderer for Notion. TS batteries included. ⚡️ - NotionX/react-notion-x&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 문서를 AI Agent에게 주고, 요구사항을 입력했다. 그리고 30분 만에 프로토타입이 완성됐다. 솔직히 이렇게 빨리 될 줄 몰랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토타입을 디자이너와 PM에게 보여줬다. &quot;노션에 글만 쓰면 이렇게 바로 페이지에 나와요. 수정도 노션에서 바로 하시면 되고요.&quot; 반응은 긍정적이었다. PM은 &quot;이제 개발자한테 수정 요청 안 해도 되는 거네요?&quot;라며 기뻐했고, 디자이너도 &quot;좋은데요?&quot;라며 동의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 PM이 글 작성 &amp;rarr; 개발자에게 전달 &amp;rarr; 개발자가 코드로 옮김 &amp;rarr; PM이 확인 &amp;rarr; 수정 요청 &amp;rarr; 개발자가 다시 수정... 이 과정이 반복됐다면, 이제는 PM이 노션에 작성 &amp;rarr; 바로 반영 &amp;rarr; PM이 직접 수정. 중간 단계가 완전히 사라졌다. 이 정도면 개발 효율이 3배는 늘었다고 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마주친 문제들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 완벽하지는 않았다. 여러 문제가 있었는데, 그 중에서 2가지만 소개하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 동기화 문제였다. PM이 노션에서 글을 작성하면 얼마 만에 페이지에 반영되게 할 것인가? 페이지 요청마다 실시간으로 노션 API를 호출하면 가장 정확하지만, API 호출 시간 때문에 페이지 로드 속도가 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민 끝에 1시간마다 페이지를 재빌드하도록 결정했다. Next.js 서버 컴포넌트에서 revalidate 옵션을 설정해 캐싱을 활용했다. 블로그 콘텐츠는 실시간으로 변경될 필요가 없었다. 1시간에 한 번 업데이트되면 충분했다. 속도를 위해 즉시성을 조금 포기한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 unstable_cache를 사용했다. 이름이 &quot;unstable&quot;이라 걱정이 되긴 했지만, 이 정도 리스크는 감수할 만했다. 블로그 페이지는 서비스의 핵심 기능이 아니고, 최악의 경우 캐시를 지우면 되니까. 실용적으로 충분히 안정적이라고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 페이지가 무거워지는 문제였다. 라이브러리를 쓰다 보니 노션 DB를 세밀하게 제어할 수 없었고, 불필요하게 많은 데이터를 로드하는 바람에 페이지가 무거워졌다. 이 부분은 클라이언트 컴포넌트를 최대한 서버 컴포넌트로 전환하고, unstable_cache를 통해 캐싱을 적극 활용해 어느 정도 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완벽한 해결은 아니었지만, 실용적으로 충분히 쓸 만했다. 라이브러리의 편리함을 택하는 대신 세밀한 제어를 포기한 것이다. 이 정도 트레이드오프는 받아들일 만했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766332150050&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;센디 블로그 - 일반용달 vs 용달이사 뭐가 다른가요?&quot; data-og-description=&quot;센디 용달을 신청하려고 보면 일반 용달과 용달 이사 중에서 어떤 서비스를 골라야 할지 헷갈릴 때가 있어요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; data-og-url=&quot;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/u8TVX/hyZPZsSxor/YG7pQ6IdqahAOiOR3VGqI1/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/VcFoj/hyZPWiAFAG/zqGlsYywuzlMfBrCkSHka0/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/ojhWN/hyZP4gEf1n/9mEZ5YwHLjQnzgPLfznfxK/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/blog/%EC%9D%BC%EB%B0%98%EC%9A%A9%EB%8B%AC-%EC%9A%A9%EB%8B%AC%EC%9D%B4%EC%82%AC-%EB%B9%84%EA%B5%90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/u8TVX/hyZPZsSxor/YG7pQ6IdqahAOiOR3VGqI1/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/VcFoj/hyZPWiAFAG/zqGlsYywuzlMfBrCkSHka0/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/ojhWN/hyZP4gEf1n/9mEZ5YwHLjQnzgPLfznfxK/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디 블로그 - 일반용달 vs 용달이사 뭐가 다른가요?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;센디 용달을 신청하려고 보면 일반 용달과 용달 이사 중에서 어떤 서비스를 골라야 할지 헷갈릴 때가 있어요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766332175201&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;센디 블로그 - 운송중 물품이 파손됐어요. 어떡하죠?&quot; data-og-description=&quot;포장되지 않은 물품은 운송 중 흔들림과 충격에 그대로 노출돼요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&quot; data-og-url=&quot;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DPwRA/hyZOEDJ1RC/GWb7Cg0bKwK9kCu0ifKbgk/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/bClVrE/hyZP0k0Gn9/3ui3KXCfcLvRWpGRmDZreK/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/cFAAjQ/hyZPUyiYVK/2OiAKHoMZy8NAmYev7nX0K/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/blog/%EB%AC%BC%ED%92%88-%ED%8C%8C%EC%86%90-%ED%8C%81&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DPwRA/hyZOEDJ1RC/GWb7Cg0bKwK9kCu0ifKbgk/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/bClVrE/hyZP0k0Gn9/3ui3KXCfcLvRWpGRmDZreK/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152,https://scrap.kakaocdn.net/dn/cFAAjQ/hyZPUyiYVK/2OiAKHoMZy8NAmYev7nX0K/img.png?width=1920&amp;amp;height=1152&amp;amp;face=0_0_1920_1152');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디 블로그 - 운송중 물품이 파손됐어요. 어떡하죠?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;포장되지 않은 물품은 운송 중 흔들림과 충격에 그대로 노출돼요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 12월 20일, 드디어 센디 블로그(&lt;a href=&quot;https://sendy.ai/blog&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sendy.ai/blog&lt;/a&gt;)를 릴리즈했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 달이라는 시간이 걸렸지만, 실제로 내가 작업한 건 첫 2주뿐이었다. 노션 콘텐츠 자동화를 구현하는 데 2주, 그 뒤 2주는 다른 이슈를 처리했고, 남은 2주 동안은 PM이 글 콘텐츠를 작성하는 시간이었다. 나는 더 이상 텍스트를 복사하고 붙여넣을 필요가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2주라는 시간을 자동화에 투자한 것은 충분히 가치 있는 선택이었다. 이제 센디 블로그뿐만 아니라, 앞으로 만들 모든 콘텐츠 페이지에 이 방식을 적용할 수 있다. 다음번엔 2주가 아니라 몇 시간이면 충분할 것이다. 한 번의 투자로 계속해서 효율을 얻는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3일간 복사 붙여넣기만 하던 시절을 생각하면, 지금의 방식이 얼마나 나아졌는지 실감한다. 개발자는 개발자다워야 한다. 단순 반복 작업에 시간을 쓰는 게 아니라, 더 나은 시스템을 만드는 데 시간을 써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;센디 용달후기 페이지로 40%의 SEO 효과를 본 만큼, 이번 블로그도 기대된다. 하지만 그보다 더 큰 성과는, 이제 우리 팀이 콘텐츠를 만드는 방식 자체가 바뀌었다는 것이다. PM은 더 편하게 글을 쓰고, 개발자는 더 의미 있는 일에 집중할 수 있게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 당신도 비슷한 반복 작업에 지쳐있다면, 한 번쯤 멈춰 서서 &quot;이 과정을 바꿀 방법은 없을까?&quot;라고 고민해보길 권한다. 생각보다 해결책은 가까이에 있을지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766332213652&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;센디 블로그 - 50살 되는 생일 날 사직서를 던졌어요, 김경태 기사님 이야기&quot; data-og-description=&quot;무역회사에서 해외 영업부로 일을 했습니다. 50살 되는 생일 날, 저에 대한 선물로 사직서를 던졌어요 (웃음)&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&quot; data-og-url=&quot;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cndGv2/hyZPUSCL8e/M8ctCMKBEK875KBhfgKKsK/img.png?width=1097&amp;amp;height=612&amp;amp;face=646_188_776_330,https://scrap.kakaocdn.net/dn/nfvuX/hyZP8XEPX6/qZKeBvGmj1ILzqpXyy4760/img.png?width=1097&amp;amp;height=612&amp;amp;face=646_188_776_330,https://scrap.kakaocdn.net/dn/phb5W/hyZP325x4q/H5A7QF2tGkWoUHcHkISfv0/img.png?width=1240&amp;amp;height=636&amp;amp;face=0_0_1240_636&quot;&gt;&lt;a href=&quot;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/blog/%EC%84%BC%EB%94%94-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EA%B9%80%EA%B2%BD%ED%83%9C-%EA%B8%B0%EC%82%AC%EB%8B%98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cndGv2/hyZPUSCL8e/M8ctCMKBEK875KBhfgKKsK/img.png?width=1097&amp;amp;height=612&amp;amp;face=646_188_776_330,https://scrap.kakaocdn.net/dn/nfvuX/hyZP8XEPX6/qZKeBvGmj1ILzqpXyy4760/img.png?width=1097&amp;amp;height=612&amp;amp;face=646_188_776_330,https://scrap.kakaocdn.net/dn/phb5W/hyZP325x4q/H5A7QF2tGkWoUHcHkISfv0/img.png?width=1240&amp;amp;height=636&amp;amp;face=0_0_1240_636');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;센디 블로그 - 50살 되는 생일 날 사직서를 던졌어요, 김경태 기사님 이야기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;무역회사에서 해외 영업부로 일을 했습니다. 50살 되는 생일 날, 저에 대한 선물로 사직서를 던졌어요 (웃음)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>커리어/Sendy</category>
      <category>sendy</category>
      <category>센디</category>
      <category>센디블로그</category>
      <category>용달</category>
      <category>화물운송</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/638</guid>
      <comments>https://insengnewbie.tistory.com/638#entry638comment</comments>
      <pubDate>Mon, 22 Dec 2025 07:30:24 +0900</pubDate>
    </item>
    <item>
      <title>UX 교과서 리뷰 &amp;ndash; 화면이 아닌 &amp;lsquo;경험 전체&amp;rsquo;를 설계한다는 것</title>
      <link>https://insengnewbie.tistory.com/637</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ccO2/dJMcai9w0Ye/NNmioDScxvNMC0tW5qF3s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ccO2/dJMcai9w0Ye/NNmioDScxvNMC0tW5qF3s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ccO2/dJMcai9w0Ye/NNmioDScxvNMC0tW5qF3s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ccO2%2FdJMcai9w0Ye%2FNNmioDScxvNMC0tW5qF3s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1984&quot; height=&quot;425&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앤서니 콘타의 『UX 교과서』는 제목 그대로 &amp;ldquo;교과서&amp;rdquo;라는 말이 전혀 아깝지 않은, 꽤 묵직한 UX 실무 입문서다. 국내판 제목은 「UX 교과서 &amp;ndash; 프로세스부터 포트폴리오까지 한 권으로 끝내는 실전 UX 디자인」, 길벗에서 출간되었고, 원제는 The Art and Science of UX다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어떤 책인가? &amp;ndash; &amp;ldquo;UX의 정석&amp;rdquo;에 가까운 실무 교과서&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자 앤서니 콘타는 비메오(Vimeo), 아마존(Amazon), 특히 아마존 뮤직에서 UX 디자이너로 일했던 실무자다. 이 책은 그 경험을 바탕으로 실제 팀과 제품 안에서 UX가 어떻게 굴러가는지를 꽤 현실적으로 보여준다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;단순히 화면 예쁘게 만드는 UI 책이 아니라, 사용자 경험을 설계하는 전체 프로세스를 처음부터 끝까지 따라가게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 속에서 다루는 범위는 꽤 넓다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;bull; UX와 UI의 차이, 헷갈리기 쉬운 개념 정리&lt;br /&gt;&amp;bull; 리서치, 아이디어 발산, 우선순위 설정, 프로토타입 제작&lt;br /&gt;&amp;bull; 페르소나와 사용자 여정 지도(Journey Map)&lt;br /&gt;&amp;bull; 실제 포트폴리오로 정리하는 방법까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 &amp;ldquo;프로세스부터 포트폴리오까지&amp;rdquo;라는 부제가 책 내용과 잘 맞는다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UX를 &amp;lsquo;화면&amp;rsquo;이 아니라 &amp;lsquo;경험 전체&amp;rsquo;로 보는 시선&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책이 좋은 이유 중 하나는 UX를 디지털 화면 안에만 가두지 않는다는 점이다.&lt;br /&gt;다른 블로그 리뷰에서도 강조하듯, 이 책은 UX를 설명할 때 병원 서비스, 쇼핑 경험, 자동차 구매와 운전, 일상 속 서비스 경험까지 예시로 끌어온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 &amp;ldquo;UX = 앱/웹 화면&amp;rdquo;이라는 좁은 이미지를 깨고, &amp;ldquo;사람이 어떤 목표를 이루는 전 과정을 어떻게 설계할 것인가?&amp;rdquo; 라는, 보다 본질적인 질문으로 시선을 넓혀 준다. 개발자나 기획자, 마케터가 읽어도 공감할 수 있는 대목이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;책의 구성과 핵심 내용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1. 디자인 싱킹 프로세스를 한 단계씩 따라가기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 디자인 싱킹 프로세스를 큰 틀로 삼고, 그 안에서 UX 디자이너가 실제로 하는 일들을 차근차근 풀어낸다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;bull; 문제를 정의하고&lt;br /&gt;&amp;bull; 사용자를 이해하고&lt;br /&gt;&amp;bull; 아이디어를 발산하고&lt;br /&gt;&amp;bull; 프로토타입을 만들고&lt;br /&gt;&amp;bull; 검증하고 개선하는 흐름까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 강의나 슬라이드에서만 보던 개념들을, 실제 프로젝트 상황과 연결해서 설명해 주는 점이 인상적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 초반에는 도널드 노먼의 디자인 싱킹 모델도 다시 짚어 주는데, UX를 오래 공부한 사람에게는 복습이 되고, 처음 접하는 사람에게는 &amp;ldquo;UX라는 분야가 어떤 사상 위에 서 있는가&amp;rdquo;를 알려주는 좋은 인트로 역할을 한다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2. 실무에서 바로 써먹을 수 있는 방법론들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책의 또 다른 강점은 실무에서 자주 거론되는 도구와 방법들을 한 번에 정리해 준다는 것. 예를 들면 이런 것들이 등장한다. ￼&lt;br /&gt;&amp;bull; 아이데이션 &amp;amp; 우선순위&lt;br /&gt;&amp;bull; 점 스티커 투표&lt;br /&gt;&amp;bull; MoSCoW 기법&lt;br /&gt;&amp;bull; 자동차 기법 등&lt;br /&gt;&amp;bull; UX 방법론&lt;br /&gt;&amp;bull; 라이트닝 데모&lt;br /&gt;&amp;bull; 프로토타입 제작&lt;br /&gt;&amp;bull; 와이어프레임&lt;br /&gt;&amp;bull; 유스 케이스&lt;br /&gt;&amp;bull; 도출 &amp;amp; 정리&lt;br /&gt;&amp;bull; 어피니티 매핑(affinity mapping)&lt;br /&gt;&amp;bull; 페르소나 작성&lt;br /&gt;&amp;bull; 사용자 여정 지도 만들기&lt;br /&gt;&amp;bull; 아이디어 발산 기법&lt;br /&gt;&amp;bull; 피자 두 판의 법칙&lt;br /&gt;&amp;bull; 크레이지 에이트(Crazy 8s)&lt;br /&gt;&amp;bull; 마인드맵&lt;br /&gt;&amp;bull; 스캠퍼(SCAMPER) 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법들이 &amp;ldquo;이름만 잔뜩 나열되는 것&amp;rdquo;에 그치지 않고, 가상의 상황과 함께 &amp;ldquo;어떤 상황에서 왜 이 방법을 쓰는지&amp;rdquo;를 설명해 주기 때문에,&lt;br /&gt;읽으면서 자연스럽게 머릿속에 툴킷이 하나씩 쌓인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;읽으면서 좋았던 점&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Korea Web Design 리뷰에서도 언급했듯이, 이 책은 실무 예시를 통해 이해를 돕는 방식이 매우 친절하다. ￼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;bull; 추상적인 개념을 먼저 던져놓고 끝내는 것이 아니라&lt;br /&gt;&amp;bull; &amp;ldquo;어떤 서비스에서 이런 문제가 있었고, 이럴 때 이런 기법을 써볼 수 있다&amp;rdquo;&lt;br /&gt;식으로 풀어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 책을 읽다 보면, &amp;ldquo;아, 우리 팀 프로젝트에도 이거 그대로 적용해볼 수 있겠는데?&amp;rdquo; 하는 생각이 자주 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;누구에게 추천할까?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해보면, 『UX 교과서』는 이런 분들께 추천하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;bull; IT 서비스 업계에서 일하는 모든 사람&lt;br /&gt;&amp;bull; UX 디자이너는 물론&lt;br /&gt;&amp;bull; 기획자, 개발자, 마케터, PO/PM 등&lt;br /&gt;&amp;bull; &amp;ldquo;사용자 경험&amp;rdquo;을 입에 올리는 모든 직군에게 추천할 만한 기본서다. ￼&lt;br /&gt;&amp;bull; UX 전환을 준비하는 디자이너&lt;br /&gt;&amp;bull; UI/그래픽/브랜딩 쪽에서 UX로 넘어가고 싶을 때&lt;br /&gt;&amp;bull; &amp;ldquo;프로세스와 방법론&amp;rdquo;을 체계적으로 익히기 좋다.&lt;br /&gt;&amp;bull; UX 실무 1~3년 차&lt;br /&gt;&amp;bull; 이미 현업에서 프로젝트를 해본 사람이 읽으면&lt;br /&gt;&amp;bull; 본인의 경험을 구조화하고, 포트폴리오에 녹이는 데 큰 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로,&lt;br /&gt;&amp;bull; 디자인 자체가 완전 처음이고&lt;br /&gt;&amp;bull; UX 용어도 하나도 모르는 상태라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;조금 더 쉬운 입문서를 한 권 먼저 읽고 나서 이 책으로 넘어오는 것도 좋은 선택이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 줄로 요약하자면&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;UX를 &amp;lsquo;예쁜 화면&amp;rsquo;이 아니라, 사람과 비즈니스 사이의 전체 경험으로 이해하고 싶은 실무자를 위한, 두꺼운 정석서.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽는 데 시간이 꽤 걸리지만, 한 번 책장을 덮고 나면 &lt;b&gt;&amp;ldquo;아, 이제 UX라는 일을 어떻게 시작해야 할지 조금은 알 것 같다&amp;rdquo;&lt;/b&gt;는 든든한 느낌을 남겨주는 책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>독서</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/637</guid>
      <comments>https://insengnewbie.tistory.com/637#entry637comment</comments>
      <pubDate>Sun, 23 Nov 2025 22:32:51 +0900</pubDate>
    </item>
    <item>
      <title>SEO 폭증을 위한 센디 용달후기 페이지 개발기</title>
      <link>https://insengnewbie.tistory.com/636</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763216554791&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;부산 용달 후기 - 부산 용달이 필요하신가요?&quot; data-og-description=&quot;부산 용달 솔직한 센디 후기! 부산 용달 비용과 인기 용달 물품, 차량별 후기, 용달 팁까지 센디에서 한번에 확인해보세요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot; data-og-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AJxIL/hyZN2QGWWX/G5XOLxHo2ayfp9ikDCciD1/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300,https://scrap.kakaocdn.net/dn/cxe1yc/hyZNStKHvY/YDXArRhAAUVypkxj9pO8V0/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300&quot;&gt;&lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AJxIL/hyZN2QGWWX/G5XOLxHo2ayfp9ikDCciD1/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300,https://scrap.kakaocdn.net/dn/cxe1yc/hyZNStKHvY/YDXArRhAAUVypkxj9pO8V0/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;부산 용달 후기 - 부산 용달이 필요하신가요?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;부산 용달 솔직한 센디 후기! 부산 용달 비용과 인기 용달 물품, 차량별 후기, 용달 팁까지 센디에서 한번에 확인해보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763216569140&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;서울 용달 후기 - 서울 용달이 필요하신가요?&quot; data-og-description=&quot;서울 용달 솔직한 센디 후기! 서울 용달 비용과 인기 용달 물품, 차량별 후기, 용달 팁까지 센디에서 한번에 확인해보세요.&quot; data-og-host=&quot;sendy.ai&quot; data-og-source-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot; data-og-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/R20he/hyZNQbDlPU/I7Q0ZrM4pmLXXFAxzjfW4k/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300,https://scrap.kakaocdn.net/dn/R8rFb/hyZNW30ewE/4lskn1vXxrj5FpeqT3pqy1/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300&quot;&gt;&lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/R20he/hyZNQbDlPU/I7Q0ZrM4pmLXXFAxzjfW4k/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300,https://scrap.kakaocdn.net/dn/R8rFb/hyZNW30ewE/4lskn1vXxrj5FpeqT3pqy1/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;서울 용달 후기 - 서울 용달이 필요하신가요?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서울 용달 솔직한 센디 후기! 서울 용달 비용과 인기 용달 물품, 차량별 후기, 용달 팁까지 센디에서 한번에 확인해보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendy.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1055&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AcbLA/dJMcah3QXlG/5hiTjE72wKsIZ5KxesEGX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AcbLA/dJMcah3QXlG/5hiTjE72wKsIZ5KxesEGX1/img.png&quot; data-alt=&quot;센디용달 후기 페이지 - 서울 서초구&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AcbLA/dJMcah3QXlG/5hiTjE72wKsIZ5KxesEGX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAcbLA%2FdJMcah3QXlG%2F5hiTjE72wKsIZ5KxesEGX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;1055&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1055&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;센디용달 후기 페이지 - 서울 서초구&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 스프린트 만에 '부산 용달' 검색 결과를 바꿨습니다. 센디(AI 기반 화물운송 매칭 플랫폼, &lt;a href=&quot;https://sendy.ai)%EC%9D%98&quot;&gt;https://sendy.ai)의&lt;/a&gt; 온페이지 SEO 개선을 위한 스쿼드 이슈로 시작한 프로젝트였습니다. 여태까지 쌓아둔 양질의 사용자 후기 데이터와 운송 데이터를 활용해 지역별 후기 페이지를 만들었고, 실제로 &quot;부산 용달&quot;, &quot;강남구 용달&quot;, &quot;부산 서울 1톤 용달&quot; 검색 시 상위에 노출되는 성과를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에는 센디용달 후기 링크를 글에 공유(백링크)하여 오프페이지 SEO를 증대시키려는 목적 또한 가지고 있습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타 업종의 성공 사례들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 &quot;부산 숙박&quot;, &quot;부산 서울 항공&quot;, &quot;대전 서울 택시&quot;를 검색해보세요. 숙박은 여기어때, 항공은 스카이스캐너, 택시는 우버가 상위에 노출됩니다. 이들은 지역 키워드와 서비스를 조합한 페이지로 SEO에 성공한 대표적인 사례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qcyF7/dJMcahv0UPR/b9XRTjvXc45bqT621hN4Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qcyF7/dJMcahv0UPR/b9XRTjvXc45bqT621hN4Uk/img.png&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1508&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.5307%; margin-right: 10px;&quot; data-widthpercent=&quot;50.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qcyF7/dJMcahv0UPR/b9XRTjvXc45bqT621hN4Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqcyF7%2FdJMcahv0UPR%2Fb9XRTjvXc45bqT621hN4Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1710&quot; height=&quot;1508&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp0jHh/dJMcafkFdUY/kdtWoB0Y0fy4hRWkXAm9D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp0jHh/dJMcafkFdUY/kdtWoB0Y0fy4hRWkXAm9D1/img.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1506&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.3065%;&quot; data-widthpercent=&quot;49.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp0jHh/dJMcafkFdUY/kdtWoB0Y0fy4hRWkXAm9D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp0jHh%2FdJMcafkFdUY%2FkdtWoB0Y0fy4hRWkXAm9D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;1506&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기회의 발견&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;b&gt;&quot;부산 서울 용달&quot;&lt;/b&gt;은 어떨까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz9Zvk/dJMcahCMy6p/PdyYtTHcXkXXIBblwXRVyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz9Zvk/dJMcahCMy6p/PdyYtTHcXkXXIBblwXRVyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz9Zvk/dJMcahCMy6p/PdyYtTHcXkXXIBblwXRVyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz9Zvk%2FdJMcahCMy6p%2FPdyYtTHcXkXXIBblwXRVyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2034&quot; height=&quot;1496&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광고를 제외하면 지역 키워드에 맞춤화된 페이지가 없었습니다. 센디에는 이미 쌓아둔 양질의 사용자 후기 데이터와 운송 데이터가 있었습니다. 여기서 기회를 발견했습니다. 최종 목표는 명확했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;부산 서울 용달&quot;을 검색했을 때 센디가 최상위에 뜨게 하는 것.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프린트!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;센디의 한 스쿼드 이슈로 프로젝트가 시작됐고, 한 스프린트 내에 마치는 것을 목표로 잡았습니다. 마침 직전에 SEO 마스터가 되기 위해 열심히 공부했던 터라 자신이 있었습니다. 기술적인 부분은 메타데이터, 시멘틱 태그, 구조화된 데이터를 적절히 적용했습니다. 더 궁금하신 분은 SEO 후기를 참고해 주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://insengnewbie.tistory.com/635&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://insengnewbie.tistory.com/635&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763216083356&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;프론트엔드 개발자가 알아야 할 SEO&quot; data-og-description=&quot;들어가며프론트엔드 개발자로서 저는 SEO를 어느 정도 안다고 생각했습니다. meta 태그를 적절히 설정하고, sitemap.xml을 관리하고, LCP나 FCP 같은 성능 지표를 최적화하면 SEO는 어느 정도 챙겼다고 &quot; data-og-host=&quot;insengnewbie.tistory.com&quot; data-og-source-url=&quot;https://insengnewbie.tistory.com/635&quot; data-og-url=&quot;https://insengnewbie.tistory.com/635&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bpnvUg/hyZNN0hENI/O4sZ69eQEyUogeVrOziZMk/img.png?width=688&amp;amp;height=481&amp;amp;face=0_0_688_481,https://scrap.kakaocdn.net/dn/zw65K/hyZNMG6kK0/znEhhfBklfgWssG6VBurw1/img.png?width=688&amp;amp;height=481&amp;amp;face=0_0_688_481,https://scrap.kakaocdn.net/dn/bneBjO/hyZNRaxpgI/c0Z6VD7TspnxCU7SPAcM2k/img.png?width=690&amp;amp;height=858&amp;amp;face=0_0_690_858&quot;&gt;&lt;a href=&quot;https://insengnewbie.tistory.com/635&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://insengnewbie.tistory.com/635&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bpnvUg/hyZNN0hENI/O4sZ69eQEyUogeVrOziZMk/img.png?width=688&amp;amp;height=481&amp;amp;face=0_0_688_481,https://scrap.kakaocdn.net/dn/zw65K/hyZNMG6kK0/znEhhfBklfgWssG6VBurw1/img.png?width=688&amp;amp;height=481&amp;amp;face=0_0_688_481,https://scrap.kakaocdn.net/dn/bneBjO/hyZNRaxpgI/c0Z6VD7TspnxCU7SPAcM2k/img.png?width=690&amp;amp;height=858&amp;amp;face=0_0_690_858');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자가 알아야 할 SEO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며프론트엔드 개발자로서 저는 SEO를 어느 정도 안다고 생각했습니다. meta 태그를 적절히 설정하고, sitemap.xml을 관리하고, LCP나 FCP 같은 성능 지표를 최적화하면 SEO는 어느 정도 챙겼다고&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;insengnewbie.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 &lt;b&gt;성공적&lt;/b&gt;이었습니다. &quot;부산 용달&quot;, &quot;부산 서울 용달&quot;, &quot;대전 용달&quot; 등의 키워드로 검색했을 때 센디의 후기 페이지가 상위에 노출되기 시작했습니다. 단순 SEO 뿐만 아니라 AI 검색 결과에도 노출되면서 AEO 증가 효과도 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0kFku/dJMcab3Dqua/akKx3X9srRo6bjzQd8z1Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0kFku/dJMcab3Dqua/akKx3X9srRo6bjzQd8z1Pk/img.png&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;981&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.9997%; margin-right: 10px;&quot; data-widthpercent=&quot;49.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0kFku/dJMcab3Dqua/akKx3X9srRo6bjzQd8z1Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0kFku%2FdJMcab3Dqua%2FakKx3X9srRo6bjzQd8z1Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;981&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xLnCi/dJMcagjzvGU/K7dlr3pMsoVIoBxeiX4Xh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xLnCi/dJMcagjzvGU/K7dlr3pMsoVIoBxeiX4Xh0/img.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;708&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.8375%;&quot; data-widthpercent=&quot;50.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xLnCi/dJMcagjzvGU/K7dlr3pMsoVIoBxeiX4Xh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxLnCi%2FdJMcagjzvGU%2FK7dlr3pMsoVIoBxeiX4Xh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCYpxF/dJMcaawTfzj/IeB2L0Wq1XCVV6oW8L9Vv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCYpxF/dJMcaawTfzj/IeB2L0Wq1XCVV6oW8L9Vv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCYpxF/dJMcaawTfzj/IeB2L0Wq1XCVV6oW8L9Vv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCYpxF%2FdJMcaawTfzj%2FIeB2L0Wq1XCVV6oW8L9Vv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;873&quot; height=&quot;249&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVbzxN/dJMcaihn9I1/RKgIZOdlIGtKaCyzQXKEKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVbzxN/dJMcaihn9I1/RKgIZOdlIGtKaCyzQXKEKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVbzxN/dJMcaihn9I1/RKgIZOdlIGtKaCyzQXKEKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVbzxN%2FdJMcaihn9I1%2FRKgIZOdlIGtKaCyzQXKEKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;892&quot; height=&quot;243&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 만들어진 페이지들을 소개하자면, &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%B6%80%EC%82%B0&quot;&gt;부산 후기&lt;/a&gt;, &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8&quot;&gt;서울 후기&lt;/a&gt;, &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EB%8C%80%EC%A0%84&quot;&gt;대전 후기&lt;/a&gt;와 같은 지역별 페이지가 있고, &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8-%EA%B0%95%EB%82%A8%EA%B5%AC&quot;&gt;서울 강남구&lt;/a&gt;처럼 더 세분화된 지역 페이지도 있습니다. &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EA%B4%91%EC%A3%BC/%EC%84%9C%EC%9A%B8&quot;&gt;광주&amp;rarr;서울&lt;/a&gt;이나 &lt;a href=&quot;https://sendy.ai/%EC%84%BC%EB%94%94%EC%9A%A9%EB%8B%AC/%ED%9B%84%EA%B8%B0/%EC%84%9C%EC%9A%B8-%EB%8F%99%EC%9E%91%EA%B5%AC/%EC%84%9C%EC%9A%B8&quot;&gt;서울 동작구&amp;rarr;서울&lt;/a&gt;처럼 출발지와 도착지를 조합한 페이지도 만들었습니다. 트래픽 증가는 장기적으로 지켜봐야 하지만, 검색 노출 자체는 빠르게 성과를 낼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배운 SEO 전략을 직접 써먹어본 재미있는 경험이었습니다. 용달이 필요하시거나, 실제 후기 페이지가 궁금하시다면 한번 둘러보세요. 생각보다 다양한 지역의 생생한 후기들을 확인하실 수 있을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/부산-강서구/서울&quot;&gt;https://sendy.ai/센디용달/후기/부산-강서구/서울&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/부산-해운대구/경기도&quot;&gt;https://sendy.ai/센디용달/후기/부산-해운대구/경기도&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/부산-수영구/서울&quot;&gt;https://sendy.ai/센디용달/후기/부산-수영구/서울&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/부산-사하구/경기도&quot;&gt;https://sendy.ai/센디용달/후기/부산-사하구/경기도&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/서울-강남구/부산&quot;&gt;https://sendy.ai/센디용달/후기/서울-강남구/부산&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/서울-송파구/경기도&quot;&gt;https://sendy.ai/센디용달/후기/서울-송파구/경기도&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/서울-마포구/부산&quot;&gt;https://sendy.ai/센디용달/후기/서울-마포구/부산&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/경기도-수원시/서울&quot;&gt;https://sendy.ai/센디용달/후기/경기도-수원시/서울&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/경기도-용인시/부산&quot;&gt;https://sendy.ai/센디용달/후기/경기도-용인시/부산&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sendy.ai/센디용달/후기/경기도-성남시/서울&quot;&gt;https://sendy.ai/센디용달/후기/경기도-성남시/서울&lt;/a&gt;&lt;/p&gt;</description>
      <category>커리어/Sendy</category>
      <category>sendy</category>
      <category>마포구용달</category>
      <category>성남용달</category>
      <category>센디</category>
      <category>송파구용달</category>
      <category>용달</category>
      <category>후기</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/636</guid>
      <comments>https://insengnewbie.tistory.com/636#entry636comment</comments>
      <pubDate>Sat, 15 Nov 2025 23:37:58 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자가 알아야 할 SEO</title>
      <link>https://insengnewbie.tistory.com/635</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TTWxh/dJMcaaDzLDF/UaVMoNbAgz1H11LJOorn4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TTWxh/dJMcaaDzLDF/UaVMoNbAgz1H11LJOorn4k/img.png&quot; data-alt=&quot;네이버 검색 결과 - 구조화된 데이터를 적용한 센디&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TTWxh/dJMcaaDzLDF/UaVMoNbAgz1H11LJOorn4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTTWxh%2FdJMcaaDzLDF%2FUaVMoNbAgz1H11LJOorn4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;670&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네이버 검색 결과 - 구조화된 데이터를 적용한 센디&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서 저는 SEO를 어느 정도 안다고 생각했습니다. meta 태그를 적절히 설정하고, sitemap.xml을 관리하고, LCP나 FCP 같은 성능 지표를 최적화하면 SEO는 어느 정도 챙겼다고 여겼죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/7Ps7fVCaVGs?si=RIpKTbKVPxik5xXQ&quot;&gt;[후원사 프로그램] 1년에 10억 원을 절약한, 강남언니의 SEO 웹 전략 공개&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=7Ps7fVCaVGs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/P9v65/hyZMKpdLAl/zSBOKBVhhJqsuHMGIRQjY0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1104_454_1160_514,https://scrap.kakaocdn.net/dn/b74hGe/hyZMPKN9He/uf5PGbLxxLNf9dSFocjwRk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1104_454_1160_514&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[후원사 프로그램] 1년에 10억 원을 절약한, 강남언니의 SEO 웹 전략 공개&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/7Ps7fVCaVGs&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 FECONF 2025에서 강남언니팀 TPO의 SEO 웹 전략 세션을 듣고 충격을 받았습니다. SEO는 단순히 페이지 성능 최적화만 있는 게 아니었습니다. 양질의 콘텐츠를 주기적으로 발행하는 '콘텐츠 SEO'와 해당 콘텐츠를 검색엔진에게 잘 전달하는 '테크니컬 SEO', 검색 결과 페이지에서의 최적화를 다루는 '온서프 SEO', 외부 링크를 관리하는 '오프페이지 SEO'까지, SEO는 훨씬 넓은 범위를 다루고 있었습니다. 심지어 요즘은 AEO(Answer Engine Optimization), GEO(Generative Engine Optimization) 같은 개념도 등장하고 있더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'내가 알던 건 SEO의 일부에 불과했구나.' 이 깨달음 이후, 저는 SEO를 제대로 공부해보기로 마음먹었습니다. &lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot;&gt;구글의 공식 SEO 기본 가이드&lt;/a&gt;를 정독하며 구조화된 데이터(Structured Data) 같은 개념을 처음 접했습니다. 검색 결과에 별점이나 FAQ가 표시되는 것이 단순한 검색 엔진의 기능이 아니라, 개발자가 직접 구현할 수 있는 것이었다는 사실이 신기했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 센디 랜딩 페이지의 SEO를 개선하는 작업을 진행하며, 테크니컬 SEO와 온서프 SEO를 함께 최적화했고, 실제로 지표가 상승하는 것을 확인할 수 있었습니다. 이번 글에서는 제가 정리한 SEO의 개념과 범위, 그리고 프론트엔드 개발자가 실제로 최적화할 수 있는 영역들을 공유하고자 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SEO가 중요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서 SEO를 신경 써야 하는 이유는 명확합니다. 우리가 만든 서비스가 사용자에게 도달하지 못한다면 아무리 좋은 코드를 작성해도 의미가 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO의 핵심 가치는 &lt;b&gt;오가닉 트래픽&lt;/b&gt;에 있습니다. 검색 엔진을 통해 자연스럽게 유입되는 트래픽은 유료 광고와 비교했을 때 몇 가지 중요한 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;b&gt;지속 가능한 트래픽 확보&lt;/b&gt;입니다. 광고는 비용을 지불하는 동안만 노출되지만, SEO를 통해 상위 노출된 페이지는 지속적으로 트래픽을 가져옵니다. 개발자가 초기에 제대로 구현해두면 장기적으로 비즈니스에 큰 기여를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;높은 신뢰도와 클릭률&lt;/b&gt;입니다. 많은 사용자들이 검색 결과의 광고 영역보다 자연 검색 결과를 더 신뢰합니다. 실제로 검색 결과 상단에 노출된 오가닉 콘텐츠는 광고 대비 높은 CTR(Click-Through Rate)을 기록하는 경향이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, &lt;b&gt;개발자가 직접 컨트롤 가능&lt;/b&gt;하다는 점입니다. 마케팅 예산이나 광고 전략은 개발자의 영역이 아니지만, SEO 최적화는 코드 레벨에서 구현할 수 있습니다. meta 태그, 구조화된 데이터, 성능 개선 등 프론트엔드 개발자가 직접 개선할 수 있는 영역이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 스타트업이나 중소 규모 서비스에서는 마케팅 예산이 제한적일 수 있기 때문에, 개발자의 SEO 역량이 서비스 성장에 직접적인 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SEO의 범위&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 프론트엔드 개발자들이 SEO를 생각할 때 &quot;페이지 성능 최적화&quot;만 떠올립니다. LCP, FCP 같은 지표를 개선하고, 이미지를 최적화하면 SEO는 충분하다고 생각하죠. 나머지는 마케터나 SEO 전문가의 영역이라고 여기는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 SEO는 훨씬 넓은 범위를 다루며, 프론트엔드 개발자가 기여할 수 있는 영역도 생각보다 많습니다. SEO는 크게 세 가지 범주로 나뉩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 온페이지 SEO (On-Page SEO)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹사이트 내부에서 이루어지는 최적화 작업&lt;/b&gt;입니다. 온페이지 SEO는 다시 두 가지로 나뉩니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테크니컬 SEO&lt;/b&gt;: 검색 엔진이 페이지를 크롤링하고 인덱싱할 수 있도록 기술적으로 최적화하는 작업입니다. HTTPS 적용, sitemap.xml 관리, robots.txt 설정, 페이지 로딩 속도 개선, 모바일 반응형 등이 여기에 포함됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콘텐츠 SEO&lt;/b&gt;: 양질의 콘텐츠를 제공하여 검색 엔진과 사용자 모두에게 가치를 전달하는 작업입니다. E-A-T(전문성, 권위성, 신뢰성) 원칙에 기반한 콘텐츠 작성이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 온서프 SEO (On-SERP SEO)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검색 결과 페이지(SERP)에서의 최적화 작업&lt;/b&gt;입니다. 검색 엔진에 잘 노출되는 것을 넘어, 검색 결과 페이지에서 경쟁사 대비 더 눈에 띄게 만들어 클릭률을 높이는 것이 목표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시가 &lt;b&gt;구조화된 데이터(Structured Data)&lt;/b&gt; 입니다. Schema.org 마크업을 활용하면 검색 결과에 별점, 가격, FAQ, 이벤트 날짜 등 추가 정보를 노출시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은, &lt;b&gt;온서프 SEO는 프론트엔드 개발자가 직접 구현할 수 있다&lt;/b&gt;는 것입니다. 구조화된 데이터는 HTML에 JSON-LD 형태로 삽입하는 것이기 때문에 개발자의 영역입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 오프페이지 SEO (Off-Page SEO)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 웹사이트와의 관계를 통해 이루어지는 최적화 작업&lt;/b&gt;입니다. 다른 사이트로부터 백링크(Backlink)를 획득하거나, 소셜 미디어에서 언급되는 등의 활동이 포함됩니다. 이 영역은 주로 마케팅팀이나 콘텐츠팀의 몫입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프론트엔드 개발자의 관점에서 정리하면:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테크니컬 SEO&lt;/b&gt;: 개발자의 핵심 영역 ✅&lt;/li&gt;
&lt;li&gt;&lt;b&gt;온서프 SEO&lt;/b&gt;: 개발자가 구현할 수 있는 영역 ✅&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콘텐츠 SEO&lt;/b&gt;: 주로 콘텐츠팀 영역, 개발자는 구조 지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오프페이지 SEO&lt;/b&gt;: 마케팅팀 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테크니컬 SEO&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테크니컬 SEO는 검색 엔진이 웹사이트를 효과적으로 크롤링하고 인덱싱할 수 있도록 기술적으로 최적화하는 작업입니다. 많은 개발자들이 이미 알고 있는 기본 사항들이 있지만, 은근히 놓치는 부분들도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적으로 챙겨야 할 것들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프론트엔드 개발자들이 이미 알고 있는 내용들입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTPS 적용&lt;/b&gt;: 구글은 HTTPS를 랭킹 요소로 공식 지정했습니다. 보안은 선택이 아닌 필수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sitemap.xml&lt;/b&gt;: 사이트의 모든 페이지를 검색 엔진에 알려주는 파일입니다. &lt;code&gt;/sitemap.xml&lt;/code&gt; 경로에 위치시키고, Google Search Console에 등록합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;robots.txt&lt;/b&gt;: 검색 엔진 크롤러의 접근을 제어하는 파일입니다. 크롤링이 불필요한 페이지(관리자 페이지, 중복 페이지 등)를 차단할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지 성능&lt;/b&gt;: LCP, FCP, FID(또는 INP) 같은 Core Web Vitals 지표를 최적화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;은근히 놓치는 것들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실무에서 다음 사항들은 의외로 누락되는 경우가 많습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Semantic HTML 태그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시멘틱 태그는 페이지의 구조를 검색 엔진에게 명확히 전달합니다. 저도 최근 작업하면서 놀란 점이, 센디의 최신 페이지들은 시멘틱 태그가 잘 적용되어 있었지만 &lt;b&gt;오래된 랜딩 페이지에는 시멘틱 태그가 하나도 없었다&lt;/b&gt;는 것입니다. 모든 요소가 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;와 &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;으로만 구성되어 있었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진은 &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; 같은 시멘틱 태그를 통해 페이지의 구조를 이해합니다. 예를 들어:&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 나쁜 예 --&amp;gt;
&amp;lt;div class=&quot;header&quot;&amp;gt;
  &amp;lt;div class=&quot;nav&quot;&amp;gt;...&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;content&quot;&amp;gt;
  &amp;lt;div class=&quot;post&quot;&amp;gt;...&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;!-- 좋은 예 --&amp;gt;
&amp;lt;header&amp;gt;
  &amp;lt;nav&amp;gt;...&amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;
&amp;lt;main&amp;gt;
  &amp;lt;article&amp;gt;...&amp;lt;/article&amp;gt;
&amp;lt;/main&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시멘틱 태그를 사용하면 검색 엔진이 &quot;이 부분이 메인 콘텐츠구나&quot;, &quot;이 부분은 네비게이션이구나&quot;를 명확히 인식할 수 있습니다. 이는 페이지의 콘텐츠 계층 구조를 이해하는 데 도움을 주고, 궁극적으로 검색 순위에 긍정적인 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Sitemap 분할&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 수가 많은 서비스라면 sitemap을 분할해야 합니다. sitemap.xml 파일 하나당 최대 50,000개의 URL과 50MB 크기 제한이 있기 때문입니다. &lt;b&gt;강남언니&lt;/b&gt;의 경우 병원, 시술, 리뷰 등 페이지가 굉장히 많아서 라우트별로 sitemap을 분할한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- sitemap-index.xml --&amp;gt;
&amp;lt;sitemapindex xmlns=&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&amp;gt;
  &amp;lt;sitemap&amp;gt;
    &amp;lt;loc&amp;gt;https://www.gangnamunni.com/api/files/sitemaps/st_static_1.xml.gz&amp;lt;/loc&amp;gt;
    &amp;lt;lastmod&amp;gt;2025-10-31&amp;lt;/lastmod&amp;gt;
  &amp;lt;/sitemap&amp;gt;
  &amp;lt;sitemap&amp;gt;
    &amp;lt;loc&amp;gt;https://www.gangnamunni.com/api/files/sitemaps/st_kr_hospitals_1.xml.gz&amp;lt;/loc&amp;gt;
    &amp;lt;lastmod&amp;gt;2025-10-31&amp;lt;/lastmod&amp;gt;
  &amp;lt;/sitemap&amp;gt;
  &amp;lt;sitemap&amp;gt;
    &amp;lt;loc&amp;gt;https://www.gangnamunni.com/api/files/sitemaps/st_jp_hospitals_1.xml.gz&amp;lt;/loc&amp;gt;
    &amp;lt;lastmod&amp;gt;2025-10-31&amp;lt;/lastmod&amp;gt;
  &amp;lt;/sitemap&amp;gt;
&amp;lt;/sitemapindex&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 센디에는 페이지가 많지 않아 분할이 필요 없지만, 서비스가 성장할 때 유용한 전략이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Canonical 태그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canonical 태그는 중복 콘텐츠 문제를 해결합니다. 같은 콘텐츠가 여러 URL로 접근 가능할 때, 검색 엔진에게 &quot;정식 URL&quot;이 무엇인지 알려주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;https://example.com/product?id=123&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://example.com/product?id=123&amp;amp;utm_source=google&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://example.com/product/123&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 세 URL이 모두 같은 콘텐츠를 보여준다면, canonical 태그로 대표 URL을 지정해야 합니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;canonical&quot; href=&quot;https://example.com/product/123&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 검색 엔진은 중복된 페이지로 인식하지 않고, 모든 SEO 가치를 하나의 URL로 집중시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;온서프 SEO&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온서프 SEO는 검색 결과 페이지(SERP)에서의 최적화 작업입니다. 검색 엔진에 잘 노출되는 것을 넘어서, &lt;b&gt;검색 결과에서 경쟁사보다 더 눈에 띄게 만들어 클릭률을 높이는 것&lt;/b&gt;이 목표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조화된 데이터 (Structured Data)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터는 제가 처음 접했을 때 가장 신기했던 개념입니다. 검색 결과에 별점이나 FAQ가 표시되는 것이 단순히 검색 엔진의 기능이 아니라, &lt;b&gt;개발자가 직접 구현할 수 있는 것&lt;/b&gt;이었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터는 Schema.org에서 정의한 표준 형식으로, HTML에 추가 정보를 제공하여 검색 엔진이 콘텐츠를 더 정확히 이해하도록 돕습니다. 이를 통해 검색 결과에 &lt;b&gt;리치 스니펫(Rich Snippet)&lt;/b&gt;이라고 불리는 향상된 정보가 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 센디 관련 키워드를 검색했을 때 다음과 같이 리치 스니펫이 표시됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccQEir/dJMcaaDzLAa/HBS5KCagxwA5iPb8SsAm6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccQEir/dJMcaaDzLAa/HBS5KCagxwA5iPb8SsAm6k/img.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;514&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.828%; margin-right: 10px;&quot; data-widthpercent=&quot;32.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccQEir/dJMcaaDzLAa/HBS5KCagxwA5iPb8SsAm6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccQEir%2FdJMcaaDzLAa%2FHBS5KCagxwA5iPb8SsAm6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FXpGQ/dJMcacVHaEs/WgV0qmyPo4iaxJxJ7OpsZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FXpGQ/dJMcacVHaEs/WgV0qmyPo4iaxJxJ7OpsZ1/img.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;241&quot; data-is-animation=&quot;false&quot; style=&quot;width: 67.0093%;&quot; data-widthpercent=&quot;67.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FXpGQ/dJMcacVHaEs/WgV0qmyPo4iaxJxJ7OpsZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFXpGQ%2FdJMcacVHaEs%2FWgV0qmyPo4iaxJxJ7OpsZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 스키마 타입과 적용 사례&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;센디 랜딩 페이지에는 다음과 같은 스키마들을 적용했습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Organization 스키마 (메인 랜딩 페이지)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 기본 정보를 검색 엔진에 알려줍니다. 로고, 연락처, 소셜 미디어 링크 등을 포함할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;application/ld+json&quot;&amp;gt;
{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;Organization&quot;,
  &quot;name&quot;: &quot;센디&quot;,
  &quot;url&quot;: &quot;https://sendy.ai&quot;,
  &quot;logo&quot;: &quot;https://sendy.ai/logo.png&quot;,
  &quot;contactPoint&quot;: {
    &quot;@type&quot;: &quot;ContactPoint&quot;,
    &quot;telephone&quot;: &quot;+82-2-1234-5678&quot;,
    &quot;contactType&quot;: &quot;Customer Service&quot;
  },
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. FAQ 스키마 (고객센터 페이지)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 묻는 질문과 답변을 구조화합니다. 검색 결과에 FAQ가 직접 표시되어 사용자가 클릭 전에 정보를 얻을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;application/ld+json&quot;&amp;gt;
{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;FAQPage&quot;,
  &quot;mainEntity&quot;: [
    {
      &quot;@type&quot;: &quot;Question&quot;,
      &quot;name&quot;: &quot;배송은 얼마나 걸리나요?&quot;,
      &quot;acceptedAnswer&quot;: {
        &quot;@type&quot;: &quot;Answer&quot;,
        &quot;text&quot;: &quot;주문 후 2-3일 내에 배송됩니다.&quot;
      }
    },
    {
      &quot;@type&quot;: &quot;Question&quot;,
      &quot;name&quot;: &quot;환불 정책은 어떻게 되나요?&quot;,
      &quot;acceptedAnswer&quot;: {
        &quot;@type&quot;: &quot;Answer&quot;,
        &quot;text&quot;: &quot;구매 후 7일 이내 환불 가능합니다.&quot;
      }
    }
  ]
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. HowTo 스키마 (가이드 페이지)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계별 가이드나 튜토리얼 콘텐츠를 구조화합니다. 검색 결과에 단계별 정보가 표시되어 사용자가 한눈에 파악할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Article 스키마&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 포스트나 뉴스 기사에 적용합니다. 작성자, 발행일, 수정일 등의 정보를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. BreadcrumbList 스키마&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지의 위치를 계층 구조로 표시합니다. 검색 결과에 breadcrumb이 표시되어 사용자가 사이트 구조를 이해하기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터는 JSON-LD 형식을 사용하는 것이 가장 권장됩니다. HTML의 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 또는 &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 태그 안에 &lt;code&gt;&amp;lt;script type=&quot;application/ld+json&quot;&amp;gt;&lt;/code&gt; 태그로 삽입하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서는 다음과 같이 구현할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;export default function Page() {
  const structuredData = {
    &quot;@context&quot;: &quot;https://schema.org&quot;,
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;센디&quot;,
    // ...
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;script
        type=&quot;application/ld+json&quot;
        dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
      /&amp;gt;
      {/* 페이지 콘텐츠 */}
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터를 추가한 후에는 &lt;a href=&quot;https://search.google.com/test/rich-results&quot;&gt;Google의 리치 결과 테스트&lt;/a&gt;에서 제대로 적용되었는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타 온서프 SEO 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터 외에도 검색 결과 페이지에서 클릭률을 높이는 요소들이 있습니다:&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Title 태그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과에 표시되는 가장 중요한 요소입니다. 50-60자 이내로 작성하며, 핵심 키워드를 앞쪽에 배치합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;title&amp;gt;센디 - 빠르고 안전한 배송 서비스 | 당일배송 가능&amp;lt;/title&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Meta Description&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과에 표시되는 설명 문구입니다. 150-160자 이내로 작성하며, 사용자가 클릭하고 싶게 만드는 문구를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;meta name=&quot;description&quot; content=&quot;센디는 당일배송이 가능한 빠르고 안전한 배송 서비스입니다. 지금 바로 주문하세요!&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Open Graph 태그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이스북, 트위터 등 소셜 미디어에 링크를 공유할 때 표시되는 정보입니다. 직접적인 검색 순위에는 영향을 주지 않지만, 소셜 트래픽 유입에 중요합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;meta property=&quot;og:title&quot; content=&quot;센디 - 빠르고 안전한 배송 서비스&quot; /&amp;gt;
&amp;lt;meta property=&quot;og:description&quot; content=&quot;당일배송이 가능한 센디를 만나보세요&quot; /&amp;gt;
&amp;lt;meta property=&quot;og:image&quot; content=&quot;https://sendy.com/og-image.png&quot; /&amp;gt;
&amp;lt;meta property=&quot;og:url&quot; content=&quot;https://sendy.com&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모니터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO 최적화 작업을 했다면, 그 효과를 측정하고 지속적으로 개선해야 합니다. 다행히 구글과 네이버는 무료로 모니터링 도구를 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Google Search Console&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 검색에서의 사이트 성과를 확인할 수 있는 필수 도구입니다. &lt;a href=&quot;https://search.google.com/search-console&quot;&gt;search.google.com/search-console&lt;/a&gt;에서 사이트를 등록하고 소유권을 인증하면 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 실적 보고서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 사용하는 기능으로, 검색 결과에서의 성과를 확인할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;노출수(Impressions)&lt;/b&gt;: 검색 결과에 내 사이트가 표시된 횟수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클릭수(Clicks)&lt;/b&gt;: 실제로 사용자가 클릭한 횟수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CTR(Click-Through Rate)&lt;/b&gt;: 클릭수 / 노출수, 검색 결과에서의 매력도를 나타냄&lt;/li&gt;
&lt;li&gt;&lt;b&gt;평균 게재 순위&lt;/b&gt;: 검색 결과에서 평균적으로 몇 번째에 노출되는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지표들을 통해 어떤 검색어로 유입되는지, 어떤 페이지의 성과가 좋은지 파악할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 색인 생성 상태&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진이 내 페이지를 제대로 크롤링하고 색인했는지 확인할 수 있습니다. 만약 특정 페이지가 색인되지 않았다면, 그 이유를 파악하고 해결할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;robots.txt로 차단됨&lt;/li&gt;
&lt;li&gt;noindex 태그 존재&lt;/li&gt;
&lt;li&gt;페이지가 발견되지 않음(sitemap에 없음)&lt;/li&gt;
&lt;li&gt;서버 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 페이지를 배포했다면 &quot;URL 검사&quot; 기능으로 색인을 수동 요청할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 개선 사항 - 리치 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터를 적용했다면, 여기서 오류 상태를 확인해야 합니다. 리치 스니펫(피처드 스니펫)이 제대로 인식되고 있는지, 오류가 있다면 어떤 오류인지 상세히 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 FAQ 스키마에 필수 필드가 누락되었거나, 잘못된 형식으로 작성된 경우 여기서 경고를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Core Web Vitals&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 성능 지표(LCP, FID, CLS)를 실제 사용자 환경에서 측정한 데이터를 제공합니다. Lighthouse와 달리 실제 사용자 데이터이기 때문에 더 신뢰할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네이버 서치어드바이저&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 시장을 타겟으로 한다면 네이버 서치어드바이저도 필수입니다. &lt;a href=&quot;https://searchadvisor.naver.com&quot;&gt;searchadvisor.naver.com&lt;/a&gt;에서 사이트를 등록할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 기능은 구글 서치 콘솔과 유사하지만, 네이버 검색 결과에 대한 데이터를 제공합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 유입 통계&lt;/li&gt;
&lt;li&gt;사이트 최적화 체크&lt;/li&gt;
&lt;li&gt;사이트맵 제출&lt;/li&gt;
&lt;li&gt;RSS 제출(블로그의 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 사용자를 대상으로 한다면 구글과 네이버 양쪽 모두 모니터링하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 SEO가 무엇인지, 그리고 SEO에서 프론트엔드 개발자가 실제로 최적화할 수 있는 영역들을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO는 단순히 meta 태그를 작성하고 페이지 성능을 개선하는 것 이상입니다. 온페이지 SEO(테크니컬 + 콘텐츠), 온서프 SEO, 오프페이지 SEO라는 세 가지 범주가 있으며, 그중 프론트엔드 개발자는 테크니컬 SEO와 온서프 SEO에서 큰 역할을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 구조화된 데이터는 검색 결과에서의 가시성을 크게 높일 수 있는 강력한 도구입니다. 시멘틱 HTML, canonical 태그 같은 기본적이지만 놓치기 쉬운 요소들도 놓치지 말아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이번에 센디 메인 랜딩의 테크니컬 SEO와 온서프 SEO를 개선하면서, 실제로 검색 노출수, CTR, 평균 게재 순위 등의 지표가 눈에 띄게 개선되는 것을 확인했습니다. Google Search Console에서 구조화된 데이터가 제대로 인식되고, 리치 스니펫이 표시되는 것을 보는 것만으로도 뿌듯했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서 우리가 작성하는 코드가 단순히 화면을 구성하는 것을 넘어, 서비스의 성장에 직접적으로 기여할 수 있다는 점이 SEO의 매력이라고 생각합니다. 이 글이 SEO를 처음 접하는 개발자분들께 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 Next.js 환경에서 테크니컬 SEO를 더 구체적으로 최적화하는 방법과, 최근 주목받고 있는 AEO(Answer Engine Optimization), GEO(Generative Engine Optimization)에 대해서도 다뤄보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글 검색엔진 최적화 기본 가이드 : &lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot;&gt;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TBWA Korea SEO Guide : &lt;a href=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot;&gt;https://seo.tbwakorea.com/website-seo/guide/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FECONF 2025 강남언니 SEO 웹 전략 : &lt;a href=&quot;https://youtu.be/7Ps7fVCaVGs?si=RIpKTbKVPxik5xXQ&quot;&gt;https://youtu.be/7Ps7fVCaVGs?si=RIpKTbKVPxik5xXQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <category>fe</category>
      <category>SEO</category>
      <category>개발자</category>
      <category>테크니컬SEO</category>
      <category>프론트엔드</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/635</guid>
      <comments>https://insengnewbie.tistory.com/635#entry635comment</comments>
      <pubDate>Fri, 31 Oct 2025 23:44:20 +0900</pubDate>
    </item>
    <item>
      <title>SEO 공부 중</title>
      <link>https://insengnewbie.tistory.com/634</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760173319539&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SEO 기본 가이드: 기본사항 | Google 검색 센터 &amp;nbsp;|&amp;nbsp; Documentation &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;검색엔진 최적화의 기본사항에 관한 지식만으로도 눈에 띄는 효과를 얻을 수 있습니다. Google SEO 기본 가이드에서 기본적인 검색엔진 최적화에 관해 간략히 알아보세요.&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot; data-og-url=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qBAPL/hyZKF254G8/xHsfFk6fB5jx2Ze1K8OcD0/img.jpg?width=2494&amp;amp;height=1400&amp;amp;face=0_0_2494_1400,https://scrap.kakaocdn.net/dn/bbMEZB/hyZKLvuUse/PH7pg0GjsVSZenobZ2GQU1/img.png?width=1600&amp;amp;height=1800&amp;amp;face=0_0_1600_1800,https://scrap.kakaocdn.net/dn/mTFFV/hyZKjsq1tL/nSBloxq0lMwrhsp5x0EiHK/img.png?width=1100&amp;amp;height=2100&amp;amp;face=0_0_1100_2100&quot;&gt;&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qBAPL/hyZKF254G8/xHsfFk6fB5jx2Ze1K8OcD0/img.jpg?width=2494&amp;amp;height=1400&amp;amp;face=0_0_2494_1400,https://scrap.kakaocdn.net/dn/bbMEZB/hyZKLvuUse/PH7pg0GjsVSZenobZ2GQU1/img.png?width=1600&amp;amp;height=1800&amp;amp;face=0_0_1600_1800,https://scrap.kakaocdn.net/dn/mTFFV/hyZKjsq1tL/nSBloxq0lMwrhsp5x0EiHK/img.png?width=1100&amp;amp;height=2100&amp;amp;face=0_0_1100_2100');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SEO 기본 가이드: 기본사항 | Google 검색 센터 &amp;nbsp;|&amp;nbsp; Documentation &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;검색엔진 최적화의 기본사항에 관한 지식만으로도 눈에 띄는 효과를 얻을 수 있습니다. Google SEO 기본 가이드에서 기본적인 검색엔진 최적화에 관해 간략히 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seo.tbwakorea.com/website-seo/guide/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760173320950&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;SEO(검색엔진 최적화)란? - 구글 네이버 가이드 총정리 | TBWA 데이터랩&quot; data-og-description=&quot;SEO (검색엔진최적화)란 자사 사이트를 검색엔진에 상위 노출시켜 자연적으로 트래픽을 높이는 디지털 마케팅입니다. SEO를 통해 웹사이트 트래픽을 높이는 방법에 대해 알아보세요!&quot; data-og-host=&quot;seo.tbwakorea.com&quot; data-og-source-url=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot; data-og-url=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/phTRy/hyZKgWK0I2/p3YOnnNR2V7QDUNkF5zuoK/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bsNBwi/hyZLaACHFH/cRTNLwRIKBpMUQwAKbJq90/img.png?width=1024&amp;amp;height=672&amp;amp;face=0_0_1024_672,https://scrap.kakaocdn.net/dn/duH34m/hyZKM2d7Fm/TkchWy9BcgPpk4XgV1Muk1/img.png?width=1024&amp;amp;height=576&amp;amp;face=0_0_1024_576&quot;&gt;&lt;a href=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seo.tbwakorea.com/website-seo/guide/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/phTRy/hyZKgWK0I2/p3YOnnNR2V7QDUNkF5zuoK/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bsNBwi/hyZLaACHFH/cRTNLwRIKBpMUQwAKbJq90/img.png?width=1024&amp;amp;height=672&amp;amp;face=0_0_1024_672,https://scrap.kakaocdn.net/dn/duH34m/hyZKM2d7Fm/TkchWy9BcgPpk4XgV1Muk1/img.png?width=1024&amp;amp;height=576&amp;amp;face=0_0_1024_576');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SEO(검색엔진 최적화)란? - 구글 네이버 가이드 총정리 | TBWA 데이터랩&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SEO (검색엔진최적화)란 자사 사이트를 검색엔진에 상위 노출시켜 자연적으로 트래픽을 높이는 디지털 마케팅입니다. SEO를 통해 웹사이트 트래픽을 높이는 방법에 대해 알아보세요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seo.tbwakorea.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안은 SEO를 개선하기 위해 페이지의 로드 속도를 빠르게 하고, 메타데이터에 키워드를 추가하는데만 집중했는데, 이번에 공부하면서 그게 전부가 아니라는 것을 느꼈다. 페이지의 성능도 중요한 지표 중의 하나겠지만, SEO에는 성능 뿐만 아니라 매우 많은 요소들이 영향을 미친다고 한다. 그리고 나는 그 많은 요소들을 그동안 놓치고 있었다... 그래도 이제 뭘 어떻게 해야 더 개선할 수 있는지 배웠으니 더 나아질 수 있을 것 같다.&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/634</guid>
      <comments>https://insengnewbie.tistory.com/634#entry634comment</comments>
      <pubDate>Sat, 11 Oct 2025 18:04:37 +0900</pubDate>
    </item>
    <item>
      <title>건강 공부 - 암</title>
      <link>https://insengnewbie.tistory.com/633</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;추석 연휴가 시작됨에 따라 평소 공부하고 싶었던 건강에 대해 공부를 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 공부라고 해도 유튜브에 검색해서 나온 조회수 많은 영상들을 정주행하는 것뿐이다. 오늘 봤던 것들은 2가지이다. 암과 치매. 최근 암에 걸리신 친척 어르신이 고생하신 것을 보고 자세히 알아보고 싶어졌다. 치매도 비슷한 맥락으로 공부를 하게 되었는데, 나보다도 부모님이 치매에 걸리게 되었을 때 내가 무엇을 어떻게 해야할지 미리 알고 싶은게 더 컸다. 우선 지식인사이드와 건강의 신 유튜브 채널과 저명한 김의신 의사 선생님이 나오는 영상 위주로 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암에 대해서는 새롭게 알게 된 정보가 많았다. 1980년대 한국에서는 암이 많이 발생하지 않았다는 점. 암 발생률은 지속적으로 증가하고 있다는 점. 기대수명이 10년이 채 남지 않았거나 노쇠한 80세 이후의 연세일 때는 암을 검진하고 치료하느라 합병증이 생기거나 말년을 더 힘들게 보낼 수도 있다는 점. 최근 20~30세 청년층에서 대장암 발병률이 늘어나고 있다는 점. 내가 평소에 많이 먹는 술, 삼겹살이 발암물질이라는 점. 특히, 고기의 탄 부분과 바베큐할 때 숯에서 올라오는 연기도 발암물질이라는 점. 김, 감태, 미역 등 바다에서 나오는 먹거리는 몸에 좋다는 점. 몸의 각 장기마다 암 종류가 20가지이고, 몸 전체에서 발생가능한 암은 500가지가 넘는다는 점. 암은 전조증상이 발생했을 때는 이미 늦은 상태이고, 안 걸리는게 최선이라는 점. 암에 걸렸다고 우울해하고 집에만 있고 남들에게 말하길 꺼리고 슬퍼하면 병이 더 악화된다는 점. 미국에서는 한국처럼 암을 두려워하기보다는 스스로 주변에 말하고, 남들이 계속 몸에 좋다고 음식을 가져다주면서 사람들과 만나고 음식을 먹으면서 체력을 회복할 수 있게하는 사회적 시선?이 존재한다는 점. 암에 걸렸을 때 암으로 죽기보다는 암에 의한 폐렴, 염증으로 인한 사망이 대부분이라는 점. 우리나라에는 비법이 너무 많다는 점. 암에 걸렸다고 특별하게 뭔가를 하기보다는 건강하게 살아가는 것이 제일 좋은 예방법이라는 점. 매운 성분인 캡사이신은 항암 효과가 있는 것은 사실이지만, 대부분의 매운 음식의 경우 맛있게 만들기 위해 단맛, 짠맛을 버무리기 때문에 항암 효과 보다는 발암 효과가 더 클 수 있다는 점.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사람은 누구나 하루에 암세포가 자라지만, 신체의 면역계가 정상적으로 동작한다면 암세포를 제거한다는 점. 그래서 영양소 부족, 수면 부족, 만성 스트레스 등으로 면역계가 무너진다면 발암 확률이 높아진다는 점.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 의사들이 강조하는 것은 건강한 생활습관이었다. 충분한 수면, 금주, 금연, 체지방 관리, 적당한 운동, 적당한 인간 관계. 생활습관이 좋지 않아서 암에 걸린 사람이 완치됐어도, 생활습관 때문에 재발할 가능성이 높다고 한다. 암은 완치됐어도 생활습관은 달라지지 않았으니까.. 그래서 지금 나의 생활습관 중에서 문제가 되는 부분이 무엇일까 생각해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 나는 한국 사람답게 삼겹살을 좋아한다. 우리 가족들도 그렇다. 그리고 최근에는 위스키에 관심이 생겨서 일주일에 6번은 술을 마시는 것 같다. 운동은 좋아해서 헬스, 복싱을 지속적으로 해오긴 했지만, 복부 지방은 항상 꽉 차있는 것 같다. 그리고 작년까지는 수면에 대해 굉장히 무관심했는데, 올해부터는 그래도 하루에 6시간 30분 이상은 자려고 노력 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 생활습관을 개선하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우선 쿠팡에서 구운 감태를 바로 주문했다. 저번 달에 참치회 먹을 때 먹으니까 맛있게 먹기도 했고.. 그리고 일주일에 술은 무조건 2번 이하로 마시기로 다짐했다. 마음 속으로 삼겹살 같은 돼지고기 보다는 다른 육류에 대한 선호도가 올라갔다. 그리고 귀찮아서 계속 미뤄왔던 건강검진을 받아보기로 했다. 수면은 하루에 7시간은 자야겠다는 생각이 들었다.&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/633</guid>
      <comments>https://insengnewbie.tistory.com/633#entry633comment</comments>
      <pubDate>Fri, 3 Oct 2025 18:58:21 +0900</pubDate>
    </item>
    <item>
      <title>현장에서 통하는 도메인 주도 설계 실전 가이드 후기</title>
      <link>https://insengnewbie.tistory.com/632</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uz7xs/btsQJHVMbgK/GgGbysSQkldKxmSYKEaJ10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uz7xs/btsQJHVMbgK/GgGbysSQkldKxmSYKEaJ10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uz7xs/btsQJHVMbgK/GgGbysSQkldKxmSYKEaJ10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuz7xs%2FbtsQJHVMbgK%2FGgGbysSQkldKxmSYKEaJ10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1984&quot; height=&quot;425&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우연한 기회로 길벗출판사의 개발자 리뷰어 프로그램에 참여하게 되었다. LLM, AI, 백엔드 등 여러 책들이 있었지만 나의 눈길을 끈 것은 이것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zyNEr/btsQHTCLHS1/tFRy3kXNXgvpxfEoe8Fd0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zyNEr/btsQHTCLHS1/tFRy3kXNXgvpxfEoe8Fd0k/img.png&quot; data-alt=&quot;전자책으로 제공받음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zyNEr/btsQHTCLHS1/tFRy3kXNXgvpxfEoe8Fd0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzyNEr%2FbtsQHTCLHS1%2FtFRy3kXNXgvpxfEoe8Fd0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;835&quot; height=&quot;533&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전자책으로 제공받음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;현장에서 통하는 도메인 주도 설계 실전 가이드&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이 책을 선택한 것은 스스로 도메인에 대한 지식이 부족하다고 생각했기 때문이다. 프로덕트 엔지니어로서 옳지 않은 말이겠지만, 스스로 도메인이나 비즈니스에 대한 관심보다는 소프트웨어 엔지니어링에 더 많은 관심을 가지고 있다고 생각한다. 다만 계속 이럴 수는 없다고 느꼈고, 이 책을 계기로 실제 비즈니스를 코드로 풀어내는 프로덕트 엔지니어로 한층 더 성장하고 싶어서 &quot;현장에서 통하는 도메인 주도 설계 실전 가이드&quot;을 읽게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책 &amp;ldquo;현장에서 통하는 도메인 주도 설계 실전 가이드&amp;rdquo;는 이름 그대로 이론 중심이 아닌 &lt;span&gt;&lt;b&gt;실무 적용&lt;/b&gt;&lt;/span&gt;을 염두에 두고 쓴 도메인 주도 설계(DDD, Domain-Driven Design) 안내서다. 복잡한 비즈니스 요구가 산재한 프로젝트 환경에서, 설계가 혼란스러워지기 쉬운 부분들을 정리하고 실제 상황에서 어떻게 모델을 세우고 경계를 그으며, 코드와 조직 구조가 도메인 설계 철학과 어떻게 맞닿을 수 있는지를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심적으로, 이 책은 추상적인 DDD 개념만 나열하는 것이 아니라 현장에서 마주치는 문제들, 예컨대 도메인 모델이 흐트러지는 지점, 협업 시 언어의 불일치, 코드가 도메인 개념을 제대로 반영하지 못하는 상황 등을 구체적으로 다룬다. 또한 여러 케이스 스터디나 작업 중 발생한 설계 타협(trade-off), 경계(boundary, context) 설정, 서비스와 모듈 나누기 같은 현실적 과제들도 제시되어, 우리 프로젝트에도 적용해볼만한 아이디어를 얻도록 유도한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 구성 면에서도, 먼저 도메인 모델링의 기본 요소(엔티티, 값 객체, 도메인 서비스 등)를 정립한 뒤, 그것들을 실제 코드 및 아키텍처 계층 또는 마이크로서비스 혹은 모듈 경계로 확대 적용하는 방식으로 진행되는 듯하다. 이렇게 이론과 실전 적용 사이의 간극을 메우려는 노력이 곳곳에 있었고 덕분에 이해하기 수월했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독자가 DDD를 처음 접하거나 이미 조금 알고 있어도, 각 단계에서 얻을 수 있는 실용적 인사이트가 많다는 것이 이 책의 장점이라고 생각한다. 특히 팀 단위 협업, 설계 유지보수, 복잡성 관리 쪽에서 고민이 있는 개발자 혹은 아키텍트에게는 유용한 안내서가 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트에서의 값객체&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값객체를 읽으며 리액트에서는 어떻게 적용해볼 수 있을지 고민해보았다. ChatGPT 와 이야기해보며 다음과 같은 커스텀 훅 패턴을 써먹어볼만하다 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758465667014&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// domain/money.ts (VO)
export type Currency = 'KRW' | 'USD' | 'EUR';
export type Money = Readonly&amp;lt;{ amount: number; currency: Currency; _tag:'Money' }&amp;gt;;

export function makeMoney(amount: number, currency: Currency): Money {
  if (!Number.isFinite(amount)) throw new Error('Invalid amount');
  const normalized = Math.round(amount * 100) / 100;
  return { amount: normalized, currency, _tag:'Money' } as const;
}

// ui/hooks/useValueObject.ts
import { useCallback, useMemo, useState } from 'react';

type Factory&amp;lt;VO, I&amp;gt; = (input: I) =&amp;gt; VO;

export function useValueObject&amp;lt;VO, I&amp;gt;(
  factory: Factory&amp;lt;VO, I&amp;gt;,
  initialInput: I
) {
  const [input, setInput] = useState&amp;lt;I&amp;gt;(initialInput);
  const [error, setError] = useState&amp;lt;string | null&amp;gt;(null);

  const value = useMemo&amp;lt;VO | null&amp;gt;(() =&amp;gt; {
    try {
      setError(null);
      return factory(input);
    } catch (e: any) {
      setError(e?.message ?? 'Invalid value');
      return null;
    }
  }, [factory, input]);

  const onChange = useCallback((next: I) =&amp;gt; setInput(next), []);
  return { input, onChange, value, error, isValid: !error &amp;amp;&amp;amp; value !== null };
}

// ui/components/PriceInput.tsx
import { useValueObject } from '../hooks/useValueObject';
import { makeMoney, type Money, type Currency } from '@/domain/money';

export function PriceInput({
  currency = 'KRW',
  initial = '0',
  onValidChange,
}: {
  currency?: Currency;
  initial?: string; // 폼은 문자열로 시작
  onValidChange?: (m: Money) =&amp;gt; void;
}) {
  // 폼 문자열 -&amp;gt; VO 로 변환
  const { input, onChange, value, error, isValid } = useValueObject(
    (raw: string) =&amp;gt; makeMoney(Number(raw.replace(/,/g, '')), currency),
    initial
  );

  // 유효할 때만 상위로 전달
  if (isValid &amp;amp;&amp;amp; value &amp;amp;&amp;amp; onValidChange) onValidChange(value);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        inputMode=&quot;decimal&quot;
        value={input}
        onChange={(e) =&amp;gt; onChange(e.target.value)}
        aria-invalid={!!error}
      /&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p role=&quot;alert&quot; style={{ color: 'crimson' }}&amp;gt;{error}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 커스텀 훅으로 값객체를 생성, 검증할 수 있게함으로써 어떤 값객체에도 재사용 가능하게 하고, 값객체를 안전하게 변환할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 통해 도메인 주도 설계가 단순히 이론적 개념이 아니라, 현장에서 팀이 더 나은 협업을 하고 복잡성을 다루는 실질적인 도구라는 걸 다시 느낄 수 있었다. 특히 유비쿼터스 언어를 바탕으로 경계를 나누고, 설계와 코드가 맞물려 돌아가는 과정은 앞으로도 계속 고민하고 싶은 주제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 책의 모든 내용을 충분히 소화한 것은 아니지만, 작은 프로젝트부터 하나씩 적용해 보면서 경험을 쌓고 싶다. 실무에서 설계의 방향성을 잡는 데 어려움을 겪고 있다면, 이 책이 분명 좋은 길잡이가 될 것이다.&lt;/p&gt;</description>
      <category>IT/코딩</category>
      <category>길벗출판사</category>
      <category>협찬</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/632</guid>
      <comments>https://insengnewbie.tistory.com/632#entry632comment</comments>
      <pubDate>Sun, 21 Sep 2025 23:46:08 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자가 알아야 할 CSRF (with Next.js)</title>
      <link>https://insengnewbie.tistory.com/631</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vxWJ8/btsQmXTBiSu/kFOEyqI0IKRA1Hwb3Dga8k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vxWJ8/btsQmXTBiSu/kFOEyqI0IKRA1Hwb3Dga8k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vxWJ8/btsQmXTBiSu/kFOEyqI0IKRA1Hwb3Dga8k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvxWJ8%2FbtsQmXTBiSu%2FkFOEyqI0IKRA1Hwb3Dga8k%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;311&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 국내외에서 보안 사고가 끊이지 않고 있습니다. yes24, 롯데카드와 같은 대형 서비스에서도 해킹 사례가 발생하는 것을 보고, 저 또한 개발자로서 보안에 관심을 가지게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프론트엔드 개발을 하면서 보안은 주로 백엔드나 인프라 영역의 문제라고만 생각해왔습니다. 그러나 실제로는 브라우저의 기본 동작, 쿠키 관리 방식, 그리고 우리가 작성하는 요청 코드 하나하나가 보안 취약점과 직접적으로 연결되어 있음을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도 특히 &lt;b&gt;CSRF&lt;/b&gt;는 프론트엔드 개발자라면 반드시 이해해야 하는 주제라고 생각합니다! 이 글에서는 CSRF의 개념을 하나씩 정리하며, 실무에서는 어떻게 방어할 수 있는지 학습했고 이를 글로써 작성해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSRF란 무엇일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)는 사용자가 의도하지 않은 요청을 브라우저가 자동으로 실행하게 만들어, 결과적으로 공격자가 원하는 동작이 서버에서 수행되도록 하는 공격 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 A 서비스(예: 은행 사이트)에 로그인합니다. 이때 서버는 세션을 유지하기 위해 브라우저에 쿠키를 내려줍니다.&lt;/li&gt;
&lt;li&gt;사용자가 로그인한 상태로 다른 웹사이트, 예를 들어 공격자가 만든 악성 사이트를 방문합니다.&lt;/li&gt;
&lt;li&gt;악성 사이트에는 A 서비스로 향하는 폼 전송이나 스크립트가 심어져 있습니다. 사용자가 버튼을 누르거나 심지어 단순히 페이지를 열기만 해도, 브라우저는 자동으로 A 서비스의 쿠키를 함께 첨부하여 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;A 서비스 입장에서는 &amp;ldquo;쿠키가 붙어 있으니 정상 사용자 요청&amp;rdquo;으로 오인하고, 계좌 이체나 비밀번호 변경 같은 크리티컬한 작업을 수행하게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 중요한 점은 &lt;b&gt;브라우저가 알아서 쿠키를 붙여주는 기본 동작&lt;/b&gt; 때문에, 사용자가 의도하지 않은 요청도 서버 입장에서는 정상적인 요청처럼 보인다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠키와 헤더의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF가 발생하는 배경에는 브라우저가 인증 정보를 다루는 방식의 차이가 있습니다. 대표적으로 &lt;b&gt;쿠키&lt;/b&gt;와 &lt;b&gt;헤더&lt;/b&gt;는 같은 &amp;ldquo;인증 수단&amp;rdquo;이지만 동작 원리가 크게 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2MX39/btsQm2HjSHT/eEQJI8SNzzAKJAd54iDaBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2MX39/btsQm2HjSHT/eEQJI8SNzzAKJAd54iDaBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2MX39/btsQm2HjSHT/eEQJI8SNzzAKJAd54iDaBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2MX39%2FbtsQm2HjSHT%2FeEQJI8SNzzAKJAd54iDaBK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;286&quot; height=&quot;344&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 쿠키는 &lt;b&gt;서버가 Set-Cookie 헤더를 내려주면 브라우저가 이를 저장해두었다가, 동일한 도메인, 경로, 프로토콜 규칙에 맞는 요청이 발생할 때마다 자동으로 붙여 보냅니다&lt;/b&gt;. 개발자가 fetch나 axios 코드에서 따로 지정하지 않아도 쿠키는 항상 따라갑니다. 사용자는 이 덕분에 로그인 상태를 유지할 수 있지만, 동시에 원치 않는 상황에서도 쿠키가 자동 전송된다는 문제가 있습니다. 바로 이 지점이 CSRF 공격의 주요 진입로가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 헤더는 상황이 다릅니다. 예를 들어 Authorization이나 X-API-Key 같은 헤더는 브라우저가 알아서 붙여주지 않습니다. &lt;b&gt;개발자가 직접 요청 코드에 작성해야만 서버로 전송됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;await fetch(&quot;/api/profile&quot;, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 임의로 민감한 헤더를 자동 전송하지 않는 이유는 명확합니다. 만약 자동으로 전송된다면, 사용자가 의도하지 않은 다른 사이트로 토큰이 흘러가는 보안 문제가 발생할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 차이는 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키는 브라우저가 알아서 관리하고 전송해주기 때문에 편리하지만 CSRF 위험이 큽니다.&lt;/li&gt;
&lt;li&gt;헤더는 개발자가 명시적으로 설정해야만 전송되므로 의도치 않은 요청이 발생할 가능성이 적습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 브라우저의 친절한 기본 동작(쿠키 자동 첨부)이 아이러니하게도 CSRF 공격의 표면을 넓히는 원인이 되는 셈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSRF 방어 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF는 브라우저의 기본 동작을 악용하는 공격이기 때문에, 서버와 클라이언트 모두에서 이를 인지하고 방어해야 합니다. 대표적으로 다음 네 가지 방법이 널리 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) CSRF 토큰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 전통적이고 널리 쓰이는 방법입니다. 서버가 사용자 세션과 연결된 랜덤 토큰을 발급하고, 클라이언트는 폼 전송이나 요청 시 이 토큰을 함께 제출해야 합니다. 서버는 토큰을 검증하여 요청이 정상적으로 발급된 것인지 확인합니다. 공격자는 이 토큰을 알 수 없으므로 위조 요청이 차단됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 서버에서 토큰 발급
const token = crypto.randomUUID();
res.cookie(&quot;csrf-token&quot;, token, { httpOnly: true });


// 클라이언트에서 요청 시 헤더에 추가
await fetch(&quot;/api/transfer&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;X-CSRF-Token&quot;: token,
  },
  body: JSON.stringify({ amount: 1000 }),
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) SameSite 쿠키 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Set-Cookie 헤더에 SameSite 속성을 설정하면, 다른 사이트에서 발생한 요청에는 쿠키가 자동 전송되지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strict: 다른 사이트에서의 요청에는 아예 쿠키를 전송하지 않음&lt;/li&gt;
&lt;li&gt;Lax: 기본값, 링크 클릭이나 GET 요청은 허용하지만 POST 같은 민감 요청은 차단&lt;/li&gt;
&lt;li&gt;None: 제3자 쿠키도 허용 (단, Secure 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) Origin 혹은 Referer 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 요청의 Origin 혹은 Referer 헤더를 검사하여, 허용된 도메인에서 온 요청인지 확인하는 방법입니다. 다만 일부 환경에서는 헤더가 비어 있을 수도 있어, 보조적인 방어 수단으로 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) 추가 인증 절차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 민감한 동작(계좌 이체, 비밀번호 변경 등)에는 비밀번호 재입력이나 OTP 같은 2차 인증을 요구하는 방법입니다. 공격자가 요청을 위조하더라도 사용자의 추가 입력이 없으면 성공할 수 없습니다. CAPTCH 처럼 봇이나 자동화 공격을 방지하는 보조 수단도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(5) 기타&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CORS 설정: 서버에서 교차 출처 요청을 제한하면, 악성 사이트가 브라우저를 통한 직접 호출을 하기 어려워집니다. 다만 CSRF 자체를 완벽히 막는 수단은 아닙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next.js 프레임워크 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 메인으로 사용하고 있는 Next.js 15 프레임워크에서 CSRF를 어떻게 방어할 수 있는 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKLDS/btsQpZB4uhV/lEYdK3lYl1mojtlKN8Qrc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKLDS/btsQpZB4uhV/lEYdK3lYl1mojtlKN8Qrc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qKLDS/btsQpZB4uhV/lEYdK3lYl1mojtlKN8Qrc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKLDS%2FbtsQpZB4uhV%2FlEYdK3lYl1mojtlKN8Qrc0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;294&quot; height=&quot;168&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Next.js는 &lt;b&gt;CSRF를 자동으로 방어해주지 않습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Router의 Route Handler(예: app/api//route.ts)나 Server Actions는 &lt;b&gt;들어온 요청을 검증 없이 그대로 핸들러로 전달&lt;/b&gt;합니다. 즉, 쿠키 기반 세션을 쓴다면 CSRF 방어는 개발자가 직접 구현해야 합니다. 반대로 &lt;b&gt;헤더 기반(JWT Bearer 등)&lt;/b&gt;은 브라우저가 자동 전송하지 않기 때문에 CSRF 표면이 상대적으로 작아집니다(대신 토큰 탈취와 XSS에 특히 주의).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Router에서 바로 쓸 수 있는 CSRF 방어 패턴에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) CSRF 토큰(Double Submit / Form Token)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이디어: 서버가 랜덤 토큰을 발급해 쿠키와 요청 바디/헤더에 동시에 담아 보내게 하고, 서버에서 둘을 비교해 일치하면 통과.&lt;/li&gt;
&lt;li&gt;장점: 프레임워크에 의존하지 않음. 폼/Server Actions에 자연스럽게 녹아듦.&lt;/li&gt;
&lt;li&gt;주의: 토큰 재사용/만료, 경로 스코프, HTTPS 전제.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버: 토큰 발급(예: 페이지 렌더링 시)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/(marketing)/page.tsx (Server Component)
import { cookies } from &quot;next/headers&quot;;
import crypto from &quot;node:crypto&quot;;

export default async function Page() {
  const token = crypto.randomUUID(); // or crypto.randomBytes(32).toString(&quot;base64url&quot;)
  cookies().set(&quot;csrf&quot;, token, {
    httpOnly: false,          // Double-submit 용도: 클라이언트에서 읽을 수 있어야 함
    sameSite: &quot;lax&quot;,
    secure: true,
    path: &quot;/&quot;,
  });

  return (
    &amp;lt;form action=&quot;/api/transfer&quot; method=&quot;POST&quot;&amp;gt;
      &amp;lt;input type=&quot;hidden&quot; name=&quot;csrf&quot; value={token} /&amp;gt;
      {/* ... */}
      &amp;lt;button type=&quot;submit&quot;&amp;gt;이체&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 라우트: 토큰 검증&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/api/transfer/route.ts
import { NextRequest, NextResponse } from &quot;next/server&quot;;

export async function POST(req: NextRequest) {
  const form = await req.formData();
  const tokenFromBody = form.get(&quot;csrf&quot;);
  const tokenFromCookie = req.cookies.get(&quot;csrf&quot;)?.value;

  if (!tokenFromBody || !tokenFromCookie || tokenFromBody !== tokenFromCookie) {
    return NextResponse.json({ error: &quot;Invalid CSRF token&quot; }, { status: 403 });
  }

  // ... 실제 비즈니스 로직
  return NextResponse.json({ ok: true });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 XHR/fetch 기반이라면, hidden input 대신 커스텀 헤더(X-CSRF-Token)로 보냅니다. 이때 토큰은 document.cookie에서 읽어 헤더에 실어보내면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) Server Actions와 함께 쓰는 CSRF 토큰 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어: 서버 컴포넌트 렌더링 시 토큰을 발급 =&amp;gt; 쿠키로 저장(클라이언트에서 읽을 수 있어야 하므로 httpOnly: false) =&amp;gt; 폼 hidden input에 같은 토큰 삽입 =&amp;gt; Server Action 내부에서 쿠키/폼 값을 비교하여 검증.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// app/transfer/page.tsx (Server Component)
import { cookies } from &quot;next/headers&quot;;
import crypto from &quot;node:crypto&quot;;

export default async function TransferPage() {
  // 1) CSRF 토큰 발급 및 쿠키 설정
  const token = crypto.randomUUID();
  cookies().set(&quot;csrf&quot;, token, {
    httpOnly: false,   // Double-submit 패턴: 클라이언트에서 읽어 hidden input에 넣기 위함
    sameSite: &quot;lax&quot;,
    secure: true,
    path: &quot;/&quot;,
  });

  // 2) Server Action 정의
  async function transferAction(formData: FormData) {
    &quot;use server&quot;;

    // 쿠키의 토큰과 폼의 토큰 비교
    const tokenFromCookie = cookies().get(&quot;csrf&quot;)?.value;
    const tokenFromForm = formData.get(&quot;csrf&quot;);

    if (!tokenFromCookie || !tokenFromForm || tokenFromCookie !== tokenFromForm) {
      // 실패 처리(에러 페이지/메시지/로그 등)
      throw new Error(&quot;Invalid CSRF token&quot;);
    }

    // 안전 통과: 실제 비즈니스 로직
    const amount = Number(formData.get(&quot;amount&quot;) || 0);
    // ... 송금 처리 등
    // 성공 후 redirect/revalidate 처리 가능
    // redirect(&quot;/transfer/success&quot;)
  }

  // 3) 폼에 hidden input으로 동일 토큰 삽입
  return (
    &amp;lt;form action={transferAction}&amp;gt;
      &amp;lt;input type=&quot;hidden&quot; name=&quot;csrf&quot; value={token} /&amp;gt;
      &amp;lt;label className=&quot;block mb-2&quot;&amp;gt;
        이체 금액
        &amp;lt;input name=&quot;amount&quot; type=&quot;number&quot; className=&quot;border p-2 ml-2&quot; /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;button type=&quot;submit&quot; className=&quot;border px-3 py-1&quot;&amp;gt;이체&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 예시는 Double-Submit Cookie 패턴입니다. 토큰을 쿠키와 폼에 동시에 담아 보내고, 서버에서 둘을 비교합니다.&lt;/li&gt;
&lt;li&gt;httpOnly: false를 사용하는 이유는 클라이언트에서 토큰 값을 읽어 hidden input에 넣기 위함입니다(대신 XSS에 더욱 주의해야함).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) SameSite 쿠키 + Origin/Referer 검증(보조)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SameSite=Lax/Strict로 제3자 컨텍스트에서 쿠키 자동 전송을 제한합니다.&lt;/li&gt;
&lt;li&gt;Origin/Referer를 화이트리스트로 검사해 다른 출처에서 온 state-changing 요청을 차단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키 발급 예시&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// 예: 로그인 응답에서 세션 쿠키 발급
import { cookies } from &quot;next/headers&quot;;

cookies().set(&quot;session&quot;, &quot;&amp;lt;opaque&amp;gt;&quot;, {
  httpOnly: true,
  secure: true,
  sameSite: &quot;lax&quot;, // 중요한 트랜잭션은 Strict 고려
  path: &quot;/&quot;,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;middleware 에서 Origin/Referer 체크(POST/PUT/PATCH/DELETE만)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// middleware.ts
import { NextResponse } from &quot;next/server&quot;;
import type { NextRequest } from &quot;next/server&quot;;

const ALLOWED = new Set([&quot;https://your.app&quot;, &quot;https://www.your.app&quot;]);

export function middleware(req: NextRequest) {
  const method = req.method.toUpperCase();
  if (![&quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;].includes(method)) {
    return NextResponse.next();
  }

  const origin = req.headers.get(&quot;origin&quot;);
  const referer = req.headers.get(&quot;referer&quot;);

  // Origin 우선 검사 =&amp;gt; 없으면 Referer의 오리진 추출
  const url = origin ?? (referer ? new URL(referer).origin : null);
  if (!url || !ALLOWED.has(url)) {
    return new NextResponse(&quot;Forbidden (CSRF check)&quot;, { status: 403 });
  }

  return NextResponse.next();
}

export const config = {
  matcher: [&quot;/((?!_next/static|_next/image|favicon.ico).*)&quot;],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 실무 조합 가이드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키 세션의 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSRF 토큰(필수) + SameSite=Lax/Strict + Origin/Referer 체크(보조) 조합을 기본값으로.&lt;/li&gt;
&lt;li&gt;민감 기능(송금, 비번 변경)은 &lt;b&gt;추가 인증(비번 재입력/OTP)&lt;/b&gt;로 한 번 더 잠가두기.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;헤더 기반(JWT Bearer 등)의 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSRF 노출면은 작지만 XSS, 토큰 탈취 위험이 커집니다. HttpOnly 쿠키를 포기했다면 &lt;b&gt;보호 헤더(Content-Security-Policy 등)&lt;/b&gt;와 보관, 갱신 전략을 촘촘히 설계하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;iron-session은 뭐하는 친구지?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 실무에서 iron-session을 무의식적으로 사용하곤 합니다. 사내 프로젝트에 기본 세팅되어 있기도 하고, Next.js 공식 문서에서도 권장하는 도구라 별 의심없이 사용했습니다. 그런데 이번에 CSRF에 대해 공부하며 이 친구에 대해 궁금해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757257580075&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Guides: Authentication | Next.js&quot; data-og-description=&quot;Learn how to implement authentication in your Next.js application.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&quot; data-og-url=&quot;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRi609/hyZI1Q6CFr/bf2myFLS92MmNVvd6Wvl5k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/n1VfL/hyZIUkAWbF/IeletA1sCEuik8omCTeOXK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/1XfZX/hyZIWbElrH/ep5LKrgYE9qBphkHx9HQP1/img.png?width=3200&amp;amp;height=2766&amp;amp;face=0_0_3200_2766&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/guides/authentication#session-management-libraries&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRi609/hyZI1Q6CFr/bf2myFLS92MmNVvd6Wvl5k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/n1VfL/hyZIUkAWbF/IeletA1sCEuik8omCTeOXK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/1XfZX/hyZIWbElrH/ep5LKrgYE9qBphkHx9HQP1/img.png?width=3200&amp;amp;height=2766&amp;amp;face=0_0_3200_2766');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Guides: Authentication | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to implement authentication in your Next.js application.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 공식 문서에서 &lt;b&gt;세션 관리(Session Management)&lt;/b&gt; 도구로 권장하는 라이브러리 중 하나인 &lt;b&gt;iron-sesison&lt;/b&gt;은, 서버가 별도 DB를 두지 않고도 암호화(및 서명)된 세션 데이터를 쿠키에 저장하도록 도와주는 라이브러리입니다. 따라서 iron-session이 쿠키를 더 안전하게 만들어줄 수는 있어도, 쿠키를 자동 전송하는 브라우저의 특성은 변함없기 때문에 CSRF에는 여전히 취약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 앞서 살펴본 CSRF 토큰 패턴을 iron-session과 조합하여 보안을 강화할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자 입장에서는 &amp;ldquo;서버에서 막아주겠지&amp;rdquo;라고 생각하기 쉽지만, 사실 쿠키의 자동 전송 방식이나 인증 전략 자체가 브라우저 동작과 깊게 맞물려 있기 때문에 클라이언트 로직을 작성하는 개발자 역시 반드시 이해해야 할 주제라는 점을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Next.js 15 처럼 프레임워크가 인증, 세션, CSRF 방어를 대신 처리해주지 않는 환경에서는, 우리가 어떤 인증 전략(쿠키 세션인지, 헤더 기반 토큰인지)을 선택하느냐에 따라 방어 방식도 달라집니다. iron-session 같은 라이브러리를 사용하면 세션 쿠키 자체는 안전하게 관리할 수 있지만, CSRF 방어는 여전히 직접 챙겨야 한다는 점을 잊지 말아야 할 것 같습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/CSRF&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/CSRF&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757258524560&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;교차 사이트 요청 위조 (CSRF) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/CSRF&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/CSRF&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/CSRF&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/CSRF&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;교차 사이트 요청 위조 (CSRF) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.hyundaimotorgroup.com/blog/465&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.hyundaimotorgroup.com/blog/465&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757258479115&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;FrontEnd 개발에서의 보안 - CSRF&quot; data-og-description=&quot;FrontEnd 개발을 할 때 고려해보면 좋을 보안에 대해 이야기합니다. 그중 CSRF(Cross Stie Request Forgery)에 대해 다루어봅니다. 이를 통해 세션, 쿠키 등에 알아보고 해당 공격에 효과적으로 대응하기 위&quot; data-og-host=&quot;developers.hyundaimotorgroup.com&quot; data-og-source-url=&quot;https://developers.hyundaimotorgroup.com/blog/465&quot; data-og-url=&quot;https://developers.hyundaimotorgroup.com/blog/465&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eyofJi/hyZIR9f0IB/sHyPaQKJYDubmXkZksefyk/img.png?width=482&amp;amp;height=464&amp;amp;face=0_0_482_464,https://scrap.kakaocdn.net/dn/8IVNQ/hyZGkZwZdo/DkBqmrrKWKBkKhC5Ppu9v0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://developers.hyundaimotorgroup.com/blog/465&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.hyundaimotorgroup.com/blog/465&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eyofJi/hyZIR9f0IB/sHyPaQKJYDubmXkZksefyk/img.png?width=482&amp;amp;height=464&amp;amp;face=0_0_482_464,https://scrap.kakaocdn.net/dn/8IVNQ/hyZGkZwZdo/DkBqmrrKWKBkKhC5Ppu9v0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;FrontEnd 개발에서의 보안 - CSRF&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;FrontEnd 개발을 할 때 고려해보면 좋을 보안에 대해 이야기합니다. 그중 CSRF(Cross Stie Request Forgery)에 대해 다루어봅니다. 이를 통해 세션, 쿠키 등에 알아보고 해당 공격에 효과적으로 대응하기 위&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.hyundaimotorgroup.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <category>csrf</category>
      <category>nextjs</category>
      <category>프론트엔드</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/631</guid>
      <comments>https://insengnewbie.tistory.com/631#entry631comment</comments>
      <pubDate>Mon, 8 Sep 2025 07:30:22 +0900</pubDate>
    </item>
    <item>
      <title>내가 누구?</title>
      <link>https://insengnewbie.tistory.com/630</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&quot;리액트 컨트리뷰터&quot;&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/benU3O/dJMb8ZB4SDs/spKLMmHnQzqQvkdCBfnVFK/tfile.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/benU3O/dJMb8ZB4SDs/spKLMmHnQzqQvkdCBfnVFK/tfile.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/benU3O/dJMb8ZB4SDs/spKLMmHnQzqQvkdCBfnVFK/tfile.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbenU3O%2FdJMb8ZB4SDs%2FspKLMmHnQzqQvkdCBfnVFK%2Ftfile.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2214&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;</description>
      <category>커리어/외부활동 기록</category>
      <category>react</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/630</guid>
      <comments>https://insengnewbie.tistory.com/630#entry630comment</comments>
      <pubDate>Sat, 6 Sep 2025 22:42:42 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자가 알아야 할 쿠키 정책</title>
      <link>https://insengnewbie.tistory.com/629</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 YES24, 롯데카드 등 굵직한 해킹 사고가 이어지면서, 프론트엔드 개발자들 사이에서도 보안에 대한 관심이 높아지고 있습니다. 저는 보안은 서버의 몫이라고 생각하고 있었는데, 이번 사건을 계기로 보안 이슈에 관심을 가지게 되었습니다! 그래서 보안에 대해 공부한 내용을 글로써 정리해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 브라우저의 쿠키에 대해 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠키 기본 이해&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각입니다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송합니다. 쿠키는 두 요청이 동일한 브라우저에서 들어왔는지 아닌지를 판단할 때 주로 사용합니다. 이를 이용하면 사용자의 로그인 상태를 유지할 수 있습니다. 상태가 없는(stateless) HTTP 프로토콜에서 상태 정보를 기억시켜주기 때문입니다.&lt;br /&gt;- MDN -&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키는 기본적으로 서버가 발급하고 브라우저가 저장하는 구조입니다. 서버는 응답 헤더에 Set-Cookie를 포함시켜 클라이언트에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 이를 저장한 뒤, 이후 동일한 도메인에 요청을 보낼 때 자동으로 Cookie 헤더에 포함시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 JavaScript 코드에서 document.cookie를 통해 쿠키를 읽거나 쓰는 것도 가능하지만, HttpOnly나 Secure 같은 중요한 보안 속성은 클라이언트에서 지정할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 보안이 중요한 세션 쿠키는 반드시 서버에서 발급하고 관리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보안 관련 핵심 속성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 속성은 서로 다른 위협을 막아줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SameSite &amp;rarr; CSRF 방어&lt;/li&gt;
&lt;li&gt;Secure &amp;rarr; HTTPS 전송 보장&lt;/li&gt;
&lt;li&gt;HttpOnly &amp;rarr; XSS 방어&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  SameSite&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교차 사이트 요청 시 쿠키 전송 여부를 제한하는 속성입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strict: 동일 사이트 요청에서만 전송 &amp;rarr; 보안 강하지만 UX 불편 가능&lt;/li&gt;
&lt;li&gt;Lax: 대부분의 안전한 요청(GET 네비게이션 등)에는 전송, 위험한 요청(POST 등)은 차단 &amp;rarr; 기본값&lt;/li&gt;
&lt;li&gt;None: 모든 교차 사이트 요청에 전송 &amp;rarr; 단, 반드시 Secure와 함께 사용해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Secure&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 연결에서만 쿠키를 전송하도록 강제합니다.이 속성이 없다면 HTTP 평문 통신에서도 쿠키가 전송되어, 중간자 공격에 노출될 수 있습니다. 따라서 운영 환경에서는 반드시 Secure를 설정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  HttpOnly&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript에서 document.cookie로 접근할 수 없도록 막습니다. 이름만 보면 HttpOnly가 &amp;ldquo;&lt;b&gt;HTTP 요청에만 쓰인다&lt;/b&gt;&amp;rdquo; 로 오해하기 쉬운데, 실제 역할은 조금 다릅니다. 이 속성을 설정하면 XSS 공격이 발생하더라도 쿠키가 노출되지 않습니다. 특히 세션 쿠키와 같이 민감한 정보는 반드시 HttpOnly로 설정하는 것이 원칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그 외 알아두면 좋은 속성들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 속성들은 보안보다는 &amp;ldquo;쿠키의 범위와 수명 관리&amp;rdquo;에 더 가깝습니다. 하지만 설정을 잘못하면 의도치 않게 다른 경로&amp;middot;서브도메인에서 쿠키가 사용되거나, 만료되지 않고 남아 있을 수 있으므로 주의가 필요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Expires / Max-Age &amp;rarr; 쿠키의 유효기간을 지정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Expires: 특정 시각까지 유효&lt;/li&gt;
&lt;li&gt;Max-Age: 초 단위로 수명 지정 (권장)&lt;/li&gt;
&lt;li&gt;보통 세션 쿠키는 Max-Age 없이 발급되어 브라우저 종료 시 삭제됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Path &amp;rarr; 쿠키가 적용되는 URL 경로를 제한합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: Path=/admin &amp;rarr; /admin 하위 경로에서만 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Domain &amp;rarr; 쿠키가 적용되는 도메인 범위를 지정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: .example.com &amp;rarr; api.example.com, shop.example.com 모두 전송&lt;/li&gt;
&lt;li&gt;필요 이상으로 넓게 설정하면 위험합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next.js 15 App Router 예제&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/api/login/route.ts (Next.js 15 App Router)
import { cookies } from 'next/headers';

export async function POST() {
  cookies().set('session', 'abc123', {
    httpOnly: true,   // JS에서 접근 불가 &amp;rarr; XSS 방어
    secure: process.env.NODE_ENV === 'production', // HTTPS 연결에서만 전송
    sameSite: 'lax',  // CSRF 방어 기본값
    path: '/',        // 사이트 전역에서 적용
  });

  return Response.json({ ok: true });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 침해 사례가 보여주듯, 보안은 서버만의 과제가 아니라는 것을 느꼈습니다. 세션 쿠키를 서버에서 발급하고 HttpOnly, Secure, SameSite를 기본값처럼 적용하는 것만으로도 XSS&amp;middot;CSRF&amp;middot;중간자 공격의 노출면을 크게 줄일 수 있다는 것을 배웠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Cookies&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Cookies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/629</guid>
      <comments>https://insengnewbie.tistory.com/629#entry629comment</comments>
      <pubDate>Sat, 6 Sep 2025 21:13:49 +0900</pubDate>
    </item>
    <item>
      <title>2025년 센디 스탠다드 그로스 해커톤 후기</title>
      <link>https://insengnewbie.tistory.com/628</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 8월 14일 목요일, 센디 제품팀 내에서 스탠다드 그로스 해커톤이 진행되었습니다! 오전 9시에 시작해 오후 6시까지 아이디어를 다듬고 발표 자료를 제출하는 일정이었으며, 결과 발표는 그다음 주 월요일인 8월 18일에 이루어졌습니다. 이번 해커톤은 단순한 아이디어 경쟁이 아니라, 팀원 각자가 생각하는 &amp;lsquo;스탠다드 그로스&amp;rsquo;의 방향을 탐색해보는 의미 있는 자리였습니다! 그 과정을 짧게나마 공유하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해커톤 일시 : 2025년 8월 14일 오전 9시 ~ 오후 6시&lt;/li&gt;
&lt;li&gt;발표 일시 : 2025년 18일 오전 10시&lt;/li&gt;
&lt;li&gt;장소 : 센디 제품팀 회의실 또는 근처 카페&lt;/li&gt;
&lt;li&gt;참여 멤버 : 연차 인원을 제외한 전 제품팀 인원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blGeJT/btsQfiiaupX/4ePau8Lhe3JfbNkRn4WsQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blGeJT/btsQfiiaupX/4ePau8Lhe3JfbNkRn4WsQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blGeJT/btsQfiiaupX/4ePau8Lhe3JfbNkRn4WsQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblGeJT%2FbtsQfiiaupX%2F4ePau8Lhe3JfbNkRn4WsQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;502&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 팀 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 해커톤에서 저(쿠키)는 제품팀의 루니, 디바, 노아와 팀을 이루었습니다. 흥미로운 점은, 저희 팀원 모두가 화주 스탠다드 업무와는 크게 인연이 없는 멤버들이었습니다. 저 역시 가끔 화주 이슈를 다루기는 하지만 메인 업무는 아니었습니다. 그래도 루니는 이전에 센디 ML 해커톤에서 우승한 경험이 있었으며, 노아는 경험 많은 시니어 개발자, 디바는 디자인과 발표 자료에 강점을 가진 팀원입니다. 이처럼 전혀 다른 배경을 가진 사람들이 뭉쳤다는 점에서 재밌는 팀 구성이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어 회의의 시작점은 단순했습니다. &amp;ldquo;스탠다드란 무엇일까?&amp;rdquo;. 디자이너 디바는 전시회를 준비하는 지인이 물품 운송에 어려움을 겪는 경험을 이야기했고, 저는 연구실 장비를 옮기기 위해 센디를 이용했던 지인의 사례를 공유했습니다. 이 과정에서 용달 서비스가 흔히 떠올리는 &amp;lsquo;이사&amp;rsquo;에만 국한되지 않고, 특정 시즌이나 특수 목적의 수요가 의외로 많다는 사실을 확인할 수 있었습니다! 저희는 이런 고객들이 어떻게 하면 센디를 자연스럽게 떠올리고 유입될 수 있을지를 집중적으로 고민했고, 그에 맞는 저희만의 답을 내놓을 수 있었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3GgL/btsQcpb0iDo/Tz19mpUPo3K5Eqtfkn0VW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3GgL/btsQcpb0iDo/Tz19mpUPo3K5Eqtfkn0VW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3GgL/btsQcpb0iDo/Tz19mpUPo3K5Eqtfkn0VW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3GgL%2FbtsQcpb0iDo%2FTz19mpUPo3K5Eqtfkn0VW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;464&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 순서로 발표하게 되서 매우 긴장되었지만, 전날밤부터 열심히 연습한대로 우리팀의 아이디어를 발표했습니다. 최종적으로 3등을 하게 되서 만족스러웠습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해마다 진행하는 센디 해커톤에는 평소에는 생각해보지 못했던 참신한 아이디어들이 많이 나왔던 것 같습니다. 작년보다도 센디 제품에 대한 이해도가 높아진 상태로 참여하니 더 즐겁게 참여할 수 있었습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2754&quot; data-origin-height=&quot;1576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3tJxW/btsQggjRYFw/EceAi1MtUOv9SBtqRjAjK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3tJxW/btsQggjRYFw/EceAi1MtUOv9SBtqRjAjK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3tJxW/btsQggjRYFw/EceAi1MtUOv9SBtqRjAjK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3tJxW%2FbtsQggjRYFw%2FEceAi1MtUOv9SBtqRjAjK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;360&quot; data-origin-width=&quot;2754&quot; data-origin-height=&quot;1576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>커리어/Sendy</category>
      <category>sendy</category>
      <category>센디</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/628</guid>
      <comments>https://insengnewbie.tistory.com/628#entry628comment</comments>
      <pubDate>Sun, 31 Aug 2025 20:07:37 +0900</pubDate>
    </item>
    <item>
      <title>FEConf 2025 짧막 후기</title>
      <link>https://insengnewbie.tistory.com/627</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3yqbV/btsP69ejmvj/PLcVzz8KrcJqW29IZvf7Ek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3yqbV/btsP69ejmvj/PLcVzz8KrcJqW29IZvf7Ek/img.jpg&quot; data-alt=&quot;세종대학교 광개토관&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3yqbV/btsP69ejmvj/PLcVzz8KrcJqW29IZvf7Ek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3yqbV%2FbtsP69ejmvj%2FPLcVzz8KrcJqW29IZvf7Ek%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;425&quot; height=&quot;567&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;세종대학교 광개토관&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 8월 23일 세종대학교 광개토관에서 열린 FEConf 2025에 다녀왔다. 그에 대한 짧막한 감상을 몇 자 적어보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0LXwD/btsP60Pqnzh/NucKOV2ruYtSwwFsUISJWK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0LXwD/btsP60Pqnzh/NucKOV2ruYtSwwFsUISJWK/img.jpg&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot; data-is-animation=&quot;false&quot; width=&quot;367&quot; height=&quot;489&quot; style=&quot;width: 35.5814%; margin-right: 10px;&quot; data-widthpercent=&quot;36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0LXwD/btsP60Pqnzh/NucKOV2ruYtSwwFsUISJWK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0LXwD%2FbtsP60Pqnzh%2FNucKOV2ruYtSwwFsUISJWK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ZTcD/btsP7mLsoaC/v2cmF3vK0HA0GIP1Xpt4N0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ZTcD/btsP7mLsoaC/v2cmF3vK0HA0GIP1Xpt4N0/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot; data-is-animation=&quot;false&quot; width=&quot;379&quot; height=&quot;284&quot; style=&quot;width: 63.2558%;&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ZTcD/btsP7mLsoaC/v2cmF3vK0HA0GIP1Xpt4N0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ZTcD%2FbtsP7mLsoaC%2Fv2cmF3vK0HA0GIP1Xpt4N0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4000&quot; height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 컨퍼런스는 세션보다는 네트워킹에 적극적으로 참여하고자 했다. 세션이야 어차피 유튜브로 올려줄텐데, 비싼 교통비와 티켓값을 내며 세션만 들으려고 부산에서 서울까지 가는 것은 아깝다고 생각했다. 그래서 처음부터 네트워킹에 적극적인 마음으로 들어갔고, 원하는 바를 이룬 채 나올 수 있었다. 광개토관 홀에 마련된 네트워킹 존에서 다양한 도메인, 다양한 연차의 사람들이 섞여 자유롭게 이야기를 할 수 있었다. 나는 지나가다가 흥미로운 주제가 들리면 은근슬쩍 끼어들어 같이 이야기를 나누기도 했다. 그리고 이번 FEConf에서는 당근모임을 통해 특정 주제에 대해 이야기할 사람을 모을 수도 있어서 FSD에 대해 이야기하는 시간을 가지기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSUWkR/btsP5vpkJaP/zxyWyXK5UKhTlLSB0VjCM1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSUWkR/btsP5vpkJaP/zxyWyXK5UKhTlLSB0VjCM1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSUWkR/btsP5vpkJaP/zxyWyXK5UKhTlLSB0VjCM1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSUWkR%2FbtsP5vpkJaP%2FzxyWyXK5UKhTlLSB0VjCM1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;447&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 달 전부터 참여한 블로그 글쓰기 모임에서 알게된 개발자 분들과도 만날 수 있었다. 이번 컨퍼런스에서 연사자(스피커)로 활동하신 문대승 님(스벨트전도사), 한상욱 님 등을 만나서 이야기를 나누었다. 특히 나는 홀로 부산에 떨어져있어서 항상 아쉬웠는데, 이번 기회를 통해 만나서 더욱 반가웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후원사 부스 존을 돌면서 굿즈를 받기도 했는데, 강남언니 부스의 개발자 분과 이야기를 나누기도 했다. 이야기 도중 어느 회사냐고 여쭤보셔서 센디라고 말씀드렸더니 한번 써봐서 알고있다고 해주셔서 나름 뿌듯(?)했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언젠가는 센디 이름을 건 부스를 내고 싶다. 그리고 나도 한 명의 엔지니어로서 지식을 공유할 수 있는 연사자로 올라서고 싶다.&lt;/p&gt;</description>
      <category>커리어/외부활동 기록</category>
      <category>FEConf</category>
      <category>개발자</category>
      <category>컨퍼런스</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/627</guid>
      <comments>https://insengnewbie.tistory.com/627#entry627comment</comments>
      <pubDate>Mon, 25 Aug 2025 21:13:02 +0900</pubDate>
    </item>
    <item>
      <title>JS 개발자를 위한 용어 정리: 모듈 vs 패키지 vs 라이브러리</title>
      <link>https://insengnewbie.tistory.com/626</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AYcyA/btsPE5d87gM/tjGbOFflXoPjkWPbRV7Vl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AYcyA/btsPE5d87gM/tjGbOFflXoPjkWPbRV7Vl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AYcyA/btsPE5d87gM/tjGbOFflXoPjkWPbRV7Vl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAYcyA%2FbtsPE5d87gM%2FtjGbOFflXoPjkWPbRV7Vl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;381&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현업에서 자바스크립트 개발을 하다 보면 &amp;lsquo;&lt;b&gt;모듈&lt;/b&gt;&amp;rsquo;, &amp;lsquo;&lt;b&gt;패키지&lt;/b&gt;&amp;rsquo;, &amp;lsquo;&lt;b&gt;라이브러리&lt;/b&gt;&amp;rsquo;라는 용어가 마치 같은 의미인 양 사용되는 경우가 많습니다! 예를 들어, 기능 단위를 분리한다는 의미로 &amp;lsquo;모듈&amp;rsquo;을, 배포 단위를 묶는다는 의미로 &amp;lsquo;패키지&amp;rsquo;를, 재사용 가능한 코드 집합이라는 의미로 &amp;lsquo;라이브러리&amp;rsquo;를 언급하지만, 서로 교차해 쓰이면서 실제 회의나 문서화 과정에서 혼선이 자주 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 저희 팀에서 React-Native 도입 여부를 논의하던 회의에서도 같은 일이 벌어졌습니다..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 웹 개발자는 &amp;ldquo;이 기능은 모듈로 쪼개서 관리하면 좋겠습니다&amp;rdquo;라고 제안했고,&lt;/li&gt;
&lt;li&gt;Android 개발자는 &amp;ldquo;패키지는 이미 설치돼 있는데요?&amp;rdquo;라며 반문했으며,&lt;/li&gt;
&lt;li&gt;iOS 개발자는 &amp;ldquo;라이브러리 의존성이 겹치는 것 같습니다&amp;rdquo;라고 지적했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자가 익숙한 맥락에서 용어를 사용했지만, 정작 모두가 동일한 개념을 떠올린 것은 아니었습니다. 그 결과 회의는 예상보다 길어졌고, 논의는 원점으로 되돌아갔습니다. 이처럼 용어 정의가 명확하지 않으면 협업 과정에서 소통 효율이 떨어지고, 불필요한 시간 낭비로 이어지기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 글에서는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일상적 의미와 타 산업군 사례를 통해 &amp;lsquo;모듈&amp;rsquo;, &amp;lsquo;패키지&amp;rsquo;, &amp;lsquo;라이브러리&amp;rsquo;의 기본 개념을 살펴보고,&lt;/li&gt;
&lt;li&gt;자바스크립트 생태계에 도입된 시기와 형태를 정리한 뒤,&lt;/li&gt;
&lt;li&gt;세 용어의 차이점을 명확히 한 다음,&lt;/li&gt;
&lt;li&gt;용어 혼동이 발생하는 원인을 분석해보겠습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번&amp;nbsp;글은&amp;nbsp;제가&amp;nbsp;공부하고&amp;nbsp;이해한&amp;nbsp;개념을&amp;nbsp;바탕으로&amp;nbsp;작성하였으며,&amp;nbsp;각&amp;nbsp;용어는&amp;nbsp;플랫폼&amp;middot;개발자&amp;middot;직군&amp;nbsp;별로&amp;nbsp;다르게&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모듈&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일상 속에서의 모듈이란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dW5SSq/btsPGt5U4Em/F9lQq1YlO4cnpq22MHkxD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dW5SSq/btsPGt5U4Em/F9lQq1YlO4cnpq22MHkxD1/img.png&quot; data-alt=&quot;기계적인 무언가?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dW5SSq/btsPGt5U4Em/F9lQq1YlO4cnpq22MHkxD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdW5SSq%2FbtsPGt5U4Em%2FF9lQq1YlO4cnpq22MHkxD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2532&quot; height=&quot;1193&quot; data-origin-width=&quot;2532&quot; data-origin-height=&quot;1193&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기계적인 무언가?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일상에서 &amp;lsquo;모듈&amp;rsquo;이라 함은 &lt;b&gt;독립적인 기능을 수행하면서도 필요에 따라 손쉽게 연결&amp;middot;분리할 수 있는 구성 요소&lt;/b&gt;를 뜻합니다. 예를 들어, 레고 블록은 각 블록이 고유한 형태와 기능을 지니되, 서로 결합하여 다양한 구조물을 완성할 수 있습니다. 가구 모듈화의 사례를 보더라도, 책장 한 칸 단위로 구매하여 원하는 대로 배열하거나 추가할 수 있다는 특징을 떠올릴 수 있습니다. 이처럼 모듈은 &lt;b&gt;&amp;lsquo;전체 시스템을 이루는 최소 단위이면서도, 다른 단위와 유연하게 결합 가능한 부품&amp;rsquo;&lt;/b&gt;으로 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타 산업군에서의 모듈이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;모듈&amp;rsquo; 개념은 소프트웨어를 넘어 다양한 산업군에서 널리 활용됩니다. 대표적인 사례 두 가지를 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자동차 산업의 모듈러 아키텍처&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현대 자동차 제조사들은 차체&amp;middot;파워트레인&amp;middot;인테리어&amp;middot;전장(전자장치) 등을 &amp;lsquo;모듈&amp;rsquo; 단위로 설계합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 동일한 섀시(차대) 위에 서로 다른 엔진 모듈이나 인포테인먼트(AV) 모듈을 결합해 다양한 차종을 생산할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이로 인해 생산 라인의 유연성이 높아지고, 부품 재사용률이 증가하며, 모델별 맞춤형 구성이 쉬워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공장 자동화 라인의 모듈화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제조 공정에서는 &amp;lsquo;작업 스테이션&amp;rsquo;이나 &amp;lsquo;로봇 셀&amp;rsquo;이 하나의 모듈로 취급됩니다.&lt;/li&gt;
&lt;li&gt;각 모듈(용접 모듈, 페인팅 모듈, 조립 모듈 등)을 필요에 따라 배치하거나 제거함으로써 공정 변경 및 확장이 용이해집니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 생산 제품이 변경될 때 전체 라인을 재구성할 필요 없이, 해당 모듈만 교체하거나 재배치하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 물리적 &amp;lsquo;부품&amp;rsquo;이나 &amp;lsquo;공정 단위&amp;rsquo;에서도 모듈은 &amp;lsquo;독립성&amp;rsquo;과 &amp;lsquo;결합 가능성&amp;rsquo;을 동시에 지닌 최소 단위로 이해됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JS 생태계에서의 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계에서 &amp;lsquo;모듈&amp;rsquo;은 코드의 재사용성과 유지보수성을 높이기 위해 도입된 독립 실행 단위를 말합니다. 대표적으로 다음 두 가지 사양이 널리 사용됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CommonJS (CJS)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js에서 채택된 모듈 방식으로, require() 함수로 다른 파일을 불러오고 module.exports 또는 exports 객체에 값을 할당해 모듈을 내보냅니다.&lt;/li&gt;
&lt;li&gt;동기적으로 파일을 로드하기 때문에 서버 사이드 실행 환경에 적합하며, 파일 경로 기반으로 모듈을 해석합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// utils.js
function sum(a, b) {
  return a + b;
}
module.exports = { sum };

// index.js
const { sum } = require('./utils');
console.log(sum(2, 3)); // 5&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ECMAScript Module (ESM)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES6(ES2015) 표준에 추가된 모듈 방식으로, import/export 키워드를 사용해 정적 분석이 가능하고 트리 쉐이킹(tree shaking)을 통해 불필요한 코드를 제거할 수 있습니다.&lt;/li&gt;
&lt;li&gt;브라우저와 Node.js(12.x 이상) 모두에서 네이티브로 지원되며, 비동기 로딩을 통해 성능 최적화가 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// math.js
export function multiply(a, b) {
  return a * b;
}

// app.js
import { multiply } from './math.js';
console.log(multiply(4, 5)); // 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 &lt;b&gt;AMD(Asynchronous Module Definition)&lt;/b&gt;나 UMD(Universal Module Definition) 같은 과도기적 사양이 있었으며, 번들러(Webpack, Rollup 등)는 이러한 모듈 사양을 통합&amp;middot;변환하여 다양한 환경에서 호환되도록 돕습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 모듈을 사용함으로써 코드 간 충돌을 방지하고, 각 기능 단위를 명확히 구분해 보다 견고한 애플리케이션 구조를 설계할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;패키지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일상 속에서의 패키지란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x85a3/btsPE31FYNv/vZfKgg7QXHdHjLGMKE9hm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x85a3/btsPE31FYNv/vZfKgg7QXHdHjLGMKE9hm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x85a3/btsPE31FYNv/vZfKgg7QXHdHjLGMKE9hm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx85a3%2FbtsPE31FYNv%2FvZfKgg7QXHdHjLGMKE9hm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;382&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일상에서 &amp;lsquo;패키지(package)&amp;rsquo;라 함은 여러 개의 물품이나 요소를 하나로 묶어 보호&amp;middot;운반&amp;middot;보관하기 쉽게 만든 형태를 뜻합니다. 예를 들어, 쇼핑몰에서 구매한 여러 상품이 하나의 박스에 담겨 배송되는 모습이나, 선물 상자에 리본을 묶어 여러 가지 선물을 하나로 구성하는 경우를 떠올릴 수 있습니다. 이처럼 패키지는 &amp;lsquo;&lt;b&gt;여러 개의 개별 품목을 하나의 단위로 포장한 묶음&amp;rsquo;&lt;/b&gt;으로 이해할 수 있으며, 내부 구성물을 외부 충격으로부터 보호하고, 분류&amp;middot;이동&amp;middot;관리의 편의성을 높여 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2586&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCDnd/btsPFg7vcK4/6kgWxUOYEU65jw22kXesKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCDnd/btsPFg7vcK4/6kgWxUOYEU65jw22kXesKK/img.png&quot; data-alt=&quot;또는 여행 패키지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCDnd/btsPFg7vcK4/6kgWxUOYEU65jw22kXesKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCDnd%2FbtsPFg7vcK4%2F6kgWxUOYEU65jw22kXesKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2586&quot; height=&quot;1416&quot; data-origin-width=&quot;2586&quot; data-origin-height=&quot;1416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;또는 여행 패키지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타 산업군에서의 패키지란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;패키지&amp;rsquo; 개념은 다양한 산업 분야에서 &amp;lsquo;묶음&amp;rsquo;과 &amp;lsquo;배포 단위&amp;rsquo;를 의미하며, 다음과 같은 사례로 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;물류택배 산업의 패키지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 제품을 하나의 박스나 팔레트에 포장하여 운송&amp;middot;보관하는 단위를 말합니다.&lt;/li&gt;
&lt;li&gt;예컨대, 이커머스 물류센터에서는 고객 주문에 따라 서로 다른 상품을 한 박스에 담아 패키징하고, 패키지 레이블로 목적지&amp;middot;무게&amp;middot;크기를 관리합니다.&lt;/li&gt;
&lt;li&gt;이를 통해 개별 상품을 일일이 취급하는 데서 오는 손실을 줄이고, 운송 효율성을 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소프트웨어 배포의 패키지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제나 플랫폼에서 설치 가능한 일련의 파일과 메타데이터를 묶은 형태를 뜻합니다.&lt;/li&gt;
&lt;li&gt;리눅스 환경에서는 .deb(Debian)나 .rpm(Red Hat) 패키지로 애플리케이션을 배포하며, 설치&amp;middot;업데이트&amp;middot;의존성 관리를 자동화합니다.&lt;/li&gt;
&lt;li&gt;윈도우에서는 설치 프로그램(Setup.exe)이나 MSI 패키지로, 모바일에서는 APK&amp;middot;IPA 파일이 패키지로 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &amp;lsquo;패키지&amp;rsquo;는 &lt;b&gt;&amp;ldquo;여러 요소를 한데 묶어 외부로 전달하거나 설치&amp;middot;보관&amp;middot;관리하기 용이한 단위&amp;rdquo;&lt;/b&gt;로 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JS 생태계에서의 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계에서 &amp;lsquo;패키지(package)&amp;rsquo;란, npm 또는 yarn 같은 패키지 매니저를 통해 등록, 배포, 설치되는 코드의 묶음을 의미합니다. 주요 특징은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메타데이터를 담은 package.json&lt;/li&gt;
&lt;li&gt;패키지 이름(name), 버전(version), 진입점(main 혹은 module), 의존성(dependencies/devDependencies) 등을 기술하는 파일입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1754325905638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-utils&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;jest&quot;
  },
  &quot;dependencies&quot;: {
    &quot;lodash&quot;: &quot;^4.17.21&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;jest&quot;: &quot;^29.0.0&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레지스트리와 퍼블리싱
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 npm 레지스트리(registry.npmjs.org)에 공개되며, 사내 전용 레지스트리(예: Verdaccio, Nexus)에도 배포할 수 있습니다.&lt;/li&gt;
&lt;li&gt;npm publish 명령어로 버전 단위의 패키지를 등록하며, 버전 관리에는 Semantic Versioning(SemVer) 규칙이 일반적으로 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;의존성 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dependencies에 등록된 패키지는 런타임 시 필요하고, devDependencies는 개발&amp;middot;테스트 도구로 사용됩니다.&lt;/li&gt;
&lt;li&gt;설치된 패키지는 node_modules 디렉터리에 트리 구조로 저장되며, 중첩된 의존성도 자동으로 해결해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;범용성 &amp;amp; 호환성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지 내부는 모듈 사양(CJS, ESM 등)과 트랜스파일 결과(Babel, TypeScript 등)에 따라 다양한 형태로 배포될 수 있습니다.&lt;/li&gt;
&lt;li&gt;번들러나 런타임이 해당 형식을 해석해 주기 때문에, 작성자는 표준 사양만 맞추면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 JS 패키지는 &lt;b&gt;&amp;ldquo;코드와 메타데이터, 의존성 정보를 함께 묶어 공유&amp;middot;설치할 수 있는 단위&amp;rdquo;&lt;/b&gt;로 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라이브러리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일상 속에서의 라이브러리란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2956&quot; data-origin-height=&quot;1408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLNHJ2/btsPIESUV6i/mryT9aJforvLMsDB9Q7HAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLNHJ2/btsPIESUV6i/mryT9aJforvLMsDB9Q7HAk/img.png&quot; data-alt=&quot;누가봐도 도서관&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLNHJ2/btsPIESUV6i/mryT9aJforvLMsDB9Q7HAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLNHJ2%2FbtsPIESUV6i%2FmryT9aJforvLMsDB9Q7HAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2956&quot; height=&quot;1408&quot; data-origin-width=&quot;2956&quot; data-origin-height=&quot;1408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;누가봐도 도서관&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일상에서 &amp;lsquo;라이브러리(library)&amp;rsquo;라 하면, 특정 주제나 목적을 위해 체계적으로 모아 놓은 &amp;lsquo;참고 자료 집합&amp;rsquo;이라고 할 수 있습니다. 예를 들어:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도서관(Library) : 여러 종류의 책과 참고문헌을 주제별&amp;middot;분야별로 분류해 비치해 두고, 이용자가 필요할 때 꺼내 볼 수 있도록 한 공간입니다.&lt;/li&gt;
&lt;li&gt;사전&amp;middot;백과사전 : 단어&amp;middot;용어의 정의, 개념, 예시 등을 모아 놓은 자료집으로, 언제든지 찾아보며 참고할 수 있다는 점에서 &amp;lsquo;라이브러리&amp;rsquo;의 본질을 잘 보여 줍니다.&lt;/li&gt;
&lt;li&gt;공구 상자(Tool Library) : 여러 도구를 분류&amp;middot;보관해 놓고, 필요할 때 꺼내 쓰도록 한 개념 역시 &amp;lsquo;라이브러리&amp;rsquo;의 확장된 의미라고 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 일상에서의 &amp;lsquo;라이브러리&amp;rsquo;는 &lt;b&gt;&amp;ldquo;필요한 정보를 모아 두고, 언제든 꺼내서 참고&amp;middot;재사용할 수 있는 자료의 집합&amp;rdquo;&lt;/b&gt;이라는 특징을 지닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타 산업군에서의 라이브러리란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;라이브러리&amp;rsquo; 개념은 소프트웨어를 넘어 다양한 분야에서 &amp;lsquo;참고&amp;middot;재사용 가능한 자산의 집합&amp;rsquo;으로 활용됩니다. 대표적인 사례를 살펴보면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;공공 도서관(Physical Library)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 주제의 책과 참고문헌을 주제별&amp;middot;분야별로 분류해 비치해 두고, 이용자가 필요할 때 꺼내 볼 수 있도록 한 공간입니다.&lt;/li&gt;
&lt;li&gt;대출&amp;middot;반납 시스템을 통해 버전 관리(발행판)와 접근 권한(회원 등급)을 운영합니다.&lt;/li&gt;
&lt;li&gt;디지털 아카이브(전자책 라이브러리)로 확장되면서 메타데이터(저자, 출판사, 키워드) 기반 검색&amp;middot;추천 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미디어 자산 라이브러리(Media Asset Library)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사진&amp;middot;영상&amp;middot;음원&amp;middot;아이콘 등 다양한 미디어 파일을 중앙 저장소에 축적하여, 프로젝트별로 꺼내 써 먹을 수 있도록 한 시스템입니다.&lt;/li&gt;
&lt;li&gt;예: 방송사 스톡 영상 라이브러리, 음원 프로덕션의 사운드 이펙트 라이브러리&lt;/li&gt;
&lt;li&gt;태그&amp;middot;카테고리&amp;middot;저작권 정보 등을 메타데이터로 관리해 손쉽게 검색하고, 버전별로 자산을 추적합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;건축&amp;middot;엔지니어링 자재 라이브러리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;건축 설계&amp;middot;시공 시 반복적으로 쓰이는 벽돌&amp;middot;창호&amp;middot;기둥&amp;middot;부속품 등의 CAD 블록이나 재료 샘플을 모아둔 컬렉션입니다.&lt;/li&gt;
&lt;li&gt;설계자는 미리 정의된 모듈(블록)을 드래그&amp;amp;드롭해 빠르게 설계 초안을 완성합니다.&lt;/li&gt;
&lt;li&gt;자재별 규격&amp;middot;강도&amp;middot;가격 정보를 메타데이터로 연동해, 시공 계획과 예산 수립에 활용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &amp;lsquo;라이브러리&amp;rsquo;는 &lt;b&gt;&amp;ldquo;필요할 때 꺼내 참고&amp;middot;사용할 수 있도록 체계적으로 모아 둔 자산의 집합&amp;rdquo;&lt;/b&gt;이라는 공통된 특징을 지닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JS 생태계에서의 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계에서 &amp;lsquo;라이브러리(library)&amp;rsquo;란, 특정 기능을 재사용 가능한 형태로 묶어놓은 코드 집합을 말합니다. 주요 특징은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 제공 중심&lt;/li&gt;
&lt;li&gt;라이브러리는 문자열 처리, 날짜 계산, UI 렌더링 등 특정 영역의 기능을 함수나 클래스 형태로 제공합니다.&lt;/li&gt;
&lt;li&gt;개발자는 import 또는 require로 라이브러리를 불러와, 라이브러리가 제공하는 API를 직접 호출하여 애플리케이션 로직을 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1754325963945&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// lodash 라이브러리 활용 예
import _ from 'lodash';
const nums = [1, 2, 3, 4];
console.log(_.shuffle(nums)); // [2,4,1,3]처럼 무작위 순서 반환&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분 npm 레지스트리에 라이브러리 단위로 퍼블리시되며, package.json의 dependencies에 추가하여 설치합니다.&lt;/li&gt;
&lt;li&gt;하나의 &amp;lsquo;패키지&amp;rsquo; 안에 여러 모듈 형식(CJS, ESM)을 포함할 수 있으며, 그중에서 라이브러리 API를 제공하는 부분이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대표적 예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React: UI 컴포넌트 생성 및 렌더링 API를 제공하는 라이브러리&lt;/li&gt;
&lt;li&gt;Lodash: 배열&amp;middot;객체&amp;middot;문자열 처리 유틸리티 함수 집합&lt;/li&gt;
&lt;li&gt;Axios: HTTP 요청을 간편하게 수행하는 API 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 JS 라이브러리는 &lt;b&gt;&amp;ldquo;개발자가 직접 호출해 쓰는, 특정 기능에 특화된 코드 묶음&amp;rdquo;&lt;/b&gt;으로 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 결국 뭐가 다른데?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqbOGQ/btsPItcRaoW/EcJo7drIFEMjKa2xkj5Rf1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqbOGQ/btsPItcRaoW/EcJo7drIFEMjKa2xkj5Rf1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqbOGQ/btsPItcRaoW/EcJo7drIFEMjKa2xkj5Rf1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqbOGQ%2FbtsPItcRaoW%2FEcJo7drIFEMjKa2xkj5Rf1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;384&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 &amp;lsquo;모듈&amp;rsquo;, &amp;lsquo;패키지&amp;rsquo;, &amp;lsquo;라이브러리&amp;rsquo;의 정의를 바탕으로 세 용어의 차이점을 정리해 보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;모듈 vs 패키지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈(Module)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;독립된 기능 단위를 책임지는 코드 파일 혹은 파일 묶음입니다.&lt;/li&gt;
&lt;li&gt;코드 내부에서 import/require로 불러와 사용하며, 재사용성과 테스트 용이성에 초점을 맞춥니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;패키지(Package)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈이나 라이브러리, 리소스, 메타데이터(package.json)를 묶어 배포&amp;middot;설치할 수 있도록 만든 단위입니다.&lt;/li&gt;
&lt;li&gt;npm/Yarn 같은 패키지 매니저를 통해 버전 관리와 의존성 해결이 자동으로 이뤄집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈은 &amp;ldquo;코드 분리&amp;middot;재사용의 최소 단위&amp;rdquo;라면, 패키지는 &amp;ldquo;그 모듈(들)을 설치&amp;middot;배포하기 위한 묶음 단위&amp;rdquo;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;패키지 vs 라이브러리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지(Package)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설치 가능한 형식적 단위로, package.json에 정의된 메타데이터를 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라이브러리(Library)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 기능(API)을 제공하는 코드 집합체로, 개발자가 직접 호출해 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지는 &amp;ldquo;배포&amp;middot;설치 단위&amp;rdquo;라는 물리적 형식에 가깝고, 라이브러리는 &amp;ldquo;기능 제공&amp;middot;재사용 단위&amp;rdquo;라는 논리적 의미에 가깝습니다.&lt;/li&gt;
&lt;li&gt;실제로 많은 라이브러리가 npm 패키지 형태로 배포되므로, 구분할 때는 &amp;lsquo;배포 형태(Package)&amp;rsquo;와 &amp;lsquo;기능 목적(Library)&amp;rsquo;을 나누어 생각하면 도움이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;모듈 vs 라이브러리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈(Module)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일&amp;nbsp;단위로&amp;nbsp;책임을&amp;nbsp;쪼갠&amp;nbsp;코드&amp;nbsp;덩어리입니다.&amp;nbsp;예를&amp;nbsp;들어&amp;nbsp;math.js&amp;nbsp;파일에&amp;nbsp;add,&amp;nbsp;subtract&amp;nbsp;함수를&amp;nbsp;정의했다면,&amp;nbsp;이것은&amp;nbsp;&amp;ldquo;모듈&amp;rdquo;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라이브러리 (Library)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러&amp;nbsp;모듈을&amp;nbsp;모아&amp;nbsp;놓은&amp;nbsp;&amp;ldquo;기능&amp;nbsp;묶음&amp;rdquo;&amp;nbsp;또는&amp;nbsp;&amp;ldquo;API&amp;nbsp;집합&amp;rdquo;입니다.&amp;nbsp;예컨대,&amp;nbsp;math.js,&amp;nbsp;string.js,&amp;nbsp;date.js&amp;nbsp;같은&amp;nbsp;여러&amp;nbsp;유틸&amp;nbsp;모듈을&amp;nbsp;한&amp;nbsp;디렉터리에&amp;nbsp;모아서&amp;nbsp;utils라는&amp;nbsp;이름으로&amp;nbsp;관리하면,&amp;nbsp;개념적으로는&amp;nbsp;이미&amp;nbsp;작은&amp;nbsp;라이브러리라&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈은 기능 한 조각이라면, 라이브러리는 그 조각들을 모아 주제별로 패키징한 덩어리입니다.&lt;/li&gt;
&lt;li&gt;라이브러리는 내부적으로 여러 모듈을 재배치&amp;middot;추상화해 단일 API 표면을 제공하지만, 모듈은 자체 API만 노출합니다.&lt;/li&gt;
&lt;li&gt;재사용 관점: 모듈은 앱 내부 재사용이 주 목적, 라이브러리는 내&amp;middot;외부 프로젝트 전반 재사용을 전제로 설계됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;왜 혼동이 생길까?&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈&amp;middot;패키지&amp;middot;라이브러리 용어가 혼용되는 배경에는 여러 가지 이유가 있습니다. 주요 원인을 정리해 보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;플랫폼별 관점 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 개발(React 등) : 웹 개발자는 주로 ES 모듈이나 CommonJS 같은 코드 단위(파일) 수준의 분리를 &amp;lsquo;모듈&amp;rsquo;이라 정의합니다.&lt;/li&gt;
&lt;li&gt;Android 개발(Java/Kotlin) : Java/Kotlin에서는 com.foo.bar 같은 네임스페이스를 &amp;lsquo;패키지&amp;rsquo;라 부르고, 배포 시에는 동일 용어가 AAR(라이브러리 아카이브) 단위로도 사용됩니다.&lt;/li&gt;
&lt;li&gt;iOS 개발(Swift/Objective-C) : iOS에서는 CocoaPods/SPM 패키지 안에 .framework 혹은 .a 형식 라이브러리가 담겨 배포됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 플랫폼마다 오래도록 사용해 온 용어 관습이 달라, 같은 단어가 전혀 다른 개념을 가리키면서 혼란이 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;역사적&amp;middot;도구적 진화 관점&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계가 성장해 오면서, 각 단계의 도구와 표준이 서로 다른 용어를 사용해 왔기에 &amp;lsquo;모듈&amp;rsquo;, &amp;lsquo;패키지&amp;rsquo;, &amp;lsquo;라이브러리&amp;rsquo;가 자연스럽게 뒤섞이게 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 브라우저 환경:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;글로벌 스크립트&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(1995 ~ 2008)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;script src=&quot;&amp;hellip;&quot;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그로 여러 파일을 불러오면서 모든 코드는 전역(global) 네임스페이스를 공유했습니다.&lt;/li&gt;
&lt;li&gt;이 시기에 &amp;lsquo;모듈&amp;rsquo;이라는 개념은 없었고, 모두가 그냥 &amp;ldquo;스크립트 파일&amp;rdquo; 혹은 &amp;ldquo;라이브러리&amp;rdquo;라고 불렀습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node.js와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CommonJS&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2009)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 사이드 JS가 부상하면서 require()&amp;middot;module.exports 방식이 표준처럼 굳어졌습니다.&lt;/li&gt;
&lt;li&gt;Node 진영에선 &amp;ldquo;모듈&amp;rdquo;이라 하면 곧바로 CommonJS 단위(파일)로, &amp;ldquo;패키지&amp;rdquo;라 하면 package.json 기반의 배포 단위로 구분하게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AMD와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RequireJS&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2010 ~ 2011)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 로딩을 위해 define()&amp;middot;require() 방식이 도입되며 &amp;lsquo;모듈&amp;rsquo;이라는 용어가 처음 쓰이기 시작했습니다.&lt;/li&gt;
&lt;li&gt;하지만 이때 &amp;lsquo;라이브러리&amp;rsquo;라 하면 여전히 jQuery&amp;middot;Lodash 같은 코드 묶음을 가리켰고, 패키지 배포 개념은 따로 없었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;npm 레지스트리의 확장&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2010 ~ 2014)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2010년 npm 레지스트리가 공개되고, 2013 ~ 2014년 사이 패키지 수가 폭발적으로 늘었습니다.&lt;/li&gt;
&lt;li&gt;전 세계 개발자가 의존성을 공유하면서, &amp;lsquo;패키지(package)&amp;rsquo;라는 용어가 배포&amp;middot;버전 관리의 대명사가 되었습니다.&lt;/li&gt;
&lt;li&gt;이 시점부터 &amp;ldquo;라이브러리&amp;rdquo;는 주로 기능 제공 목적의 패키지를, &amp;ldquo;패키지&amp;rdquo;는 그 배포 단위를 의미하는 용어로 자리잡았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;번들러와 트랜스파일러의 등장&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2012 ~ 2015)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Webpack (2012), Babel (2014), Rollup (2015) 등이 CJS&amp;middot;ESM&amp;middot;AMD를 모두 처리하면서, 개발자는 &amp;lsquo;모듈 포맷(module format)&amp;rsquo;과 &amp;lsquo;패키지 설정(package configuration)&amp;rsquo;을 동시에 다뤄야 했습니다.&lt;/li&gt;
&lt;li&gt;개발자는 모듈 포맷과 패키지 설정을 동시에 신경 써야 했고, 용어 혼선이 가속화됐습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ES 모듈 표준화&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2015)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언어 사양 차원에서 import/export가 도입되면서, 브라우저&amp;middot;Node.js 간 모듈 개념이 통합되었습니다.&lt;/li&gt;
&lt;li&gt;그러나 &amp;ldquo;패키지&amp;rdquo; 필드를 읽어들이는 로더(loader)들이 다양하게 등장하면서, module&amp;middot;main&amp;middot;browser 등 메타데이터 해석 방식이 엇갈려 용어 혼선을 가중시켰습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모노레포 &amp;amp; 워크스페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lerna (2015), Yarn Workspaces (2017), pnpm Workspaces (2017) 등이 다수 패키지를 하나의 저장소에 관리하는 문화를 퍼뜨렸습니다.&lt;/li&gt;
&lt;li&gt;이 과정에서 &amp;lsquo;모듈&amp;rsquo;은 다시 &amp;ldquo;패키지 안의 작은 단위&amp;rdquo;로, &amp;lsquo;패키지&amp;rsquo;는 &amp;ldquo;모노레포 내 하나의 배포 단위&amp;rdquo;로 의미가 세분화되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Node.js 네이티브 ESM 지원&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2020)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js 12 LTS(2020)부터 ESM이 안정적으로 지원되면서 서버&amp;middot;클라이언트 모두 동일한 모듈 사양을 사용할 수 있게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 각 단계에서 도입된 도구와 표준이 조금씩 다른 용어를 정의해 왔기 때문에, 오늘날 개발자들은 문맥에 따라 같은 단어를 전혀 다른 개념으로 사용하게 된 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;다른 직군과 소통할 때&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 직군(디자이너 또는 PM 등)은 모듈&amp;middot;패키지&amp;middot;라이브러리라는 단어를 &amp;lsquo;일상적 의미&amp;rsquo;로 받아들일 가능성이 높습니다. &amp;lsquo;모듈&amp;rsquo;이라고 하면 기계 부품처럼 기계적인 무언가를 떠올릴 것이고, &amp;lsquo;패키지&amp;rsquo;라 하면 여행 패키지/선물 패키지, &amp;lsquo;라이브러리&amp;rsquo;라 하면 도서관을 떠올릴 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;FAQ&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;package.json 이름은 왜 &amp;lsquo;package&amp;rsquo;인가?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;package.json은 패키지(package) 단위로 코드와 메타데이터를 묶기 위한 설정 파일입니다. 과거 Node.js 생태계 초창기에 npm(Node Package Manager)이 &amp;ldquo;코드 묶음&amp;rdquo;을 패키지라고 정의하면서, 그 묶음의 속성을 기록하는 파일로 package.json이 자리잡았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;dependencies에 적힌 건 라이브러리일까, 패키지일까?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;dependencies 필드에는 설치할 대상을 패키지 단위로 등록합니다. 그 안에 실제로 담긴 것은 특정 기능을 제공하는 라이브러리(library) 일 수도 있고, 단순 모듈일 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, dependencies에는 배포&amp;middot;설치 단위인 패키지 이름이 적히며, 그 패키지 내부에 포함된 코드가 라이브러리나 모듈로 동작하는 구조입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;node_modules에 설치된 것은 라이브러리인가, 패키지인가?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;node_modules 폴더에는 여러 패키지(package) 디렉터리가 위치합니다. 각 디렉터리(예: lodash/, react/)가 하나의 패키지 단위로 설치된 것이고, 그 안에 여러 개의 모듈과 라이브러리가 포함되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 node_modules에는 &amp;ldquo;라이브러리&amp;rdquo;나 &amp;ldquo;모듈&amp;rdquo;이 들어 있는 여러 패키지가 모여 있다고 이해할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;React 프로젝트에 만든 shared/hooks 또는 shared/components는 모듈일까, 라이브러리일까?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;shared/hooks와 shared/components는 프로젝트 내에서 특정 기능(훅, 컴포넌트)을 캡슐화한 모듈(module) 단위로 시작합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 이들을 별도의 배포&amp;middot;버전 관리 단위로 묶어 npm private registry나 mono-repo 방식으로 관리한다면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;라이브러리(library)&lt;/b&gt;로 간주할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;퍼스트파티(first-party) 라이브러리란, 내 조직(또는 내 프로젝트)에서 직접 만들고 유지&amp;middot;관리하는 라이브러리를 의미하므로, 위 경우에는 퍼스트파티 라이브러리라고 부를 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번&amp;nbsp;글에서는&amp;nbsp;제가&amp;nbsp;공부하고&amp;nbsp;이해한&amp;nbsp;내용을&amp;nbsp;글로써&amp;nbsp;정리해보았습니다.&amp;nbsp;그러나&amp;nbsp;공부할수록&amp;nbsp;누구나&amp;nbsp;공유하는&amp;nbsp;명확한&amp;nbsp;정의는&amp;nbsp;존재하지&amp;nbsp;않는다는&amp;nbsp;느낌을&amp;nbsp;받았습니다.&amp;nbsp;따라서&amp;nbsp;이&amp;nbsp;정의가&amp;nbsp;옳다고&amp;nbsp;밀어붙이기보다는&amp;nbsp;대화의&amp;nbsp;맥락과&amp;nbsp;흐름&amp;nbsp;속에서&amp;nbsp;서로&amp;nbsp;같은&amp;nbsp;이미지를&amp;nbsp;그릴&amp;nbsp;수&amp;nbsp;있다면&amp;nbsp;더&amp;nbsp;효율적인&amp;nbsp;이야기를&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;거라고&amp;nbsp;생각합니다!&lt;br /&gt;&lt;br /&gt;저와&amp;nbsp;같은&amp;nbsp;경험을&amp;nbsp;했던&amp;nbsp;분들에게&amp;nbsp;이번&amp;nbsp;글이&amp;nbsp;도움이&amp;nbsp;되었으면&amp;nbsp;좋겠습니다&amp;nbsp;:)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;참조&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/&quot;&gt;https://www.npmjs.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/626</guid>
      <comments>https://insengnewbie.tistory.com/626#entry626comment</comments>
      <pubDate>Tue, 5 Aug 2025 07:00:02 +0900</pubDate>
    </item>
    <item>
      <title>나의 학습 방법 : 아티클 구독</title>
      <link>https://insengnewbie.tistory.com/625</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 저의 학습 방법 중 하나를 글로 정리해보려고 해요. AI, 책, 인터넷 강의, 네트워킹 등 많은 방법이 있겠지만, 저는 평소에는 주로 이 방법으로 가장 많은 지식을 습득합니다. 그것은 다름 아닌... &lt;b&gt;&quot;아티클 구독&quot;&lt;/b&gt;입니다. 사실 전혀 특별할게 없죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아티클을 읽는 것에는 여러 장점이 있지만, 저는 가장 최신의 정보를 정돈된 상태로 얻을 수 있어서 좋아합니다. &lt;b&gt;책&lt;/b&gt;, &lt;b&gt;인터넷 강의&lt;/b&gt;야 말할 것도 없고, 웹 서칭이 가능한 &lt;b&gt;ChatGPT&lt;/b&gt;, &lt;b&gt;Perplexity&lt;/b&gt;으로도 충분히 최신 정보에 접근이 가능하지만, LLM들은 제가 질문을 하지 않으면 아무런 인사이트도 주지 않습니다. &lt;b&gt;네트워킹&lt;/b&gt;으로 저보다 뛰어난 개발자분들을 만나서 이야기해볼 수 있으면 가장 좋겠지만, 아쉽게도 저는 부산에서 근무하고 있는데다 서울경기권에 갈 일이 적어서 네트워킹도 쉽지 않습니다ㅠ 이런 저에게 아티클은 &lt;b&gt;각 분야의 전문가 분들이, 최신의 정보 또는 실무의 문제 해결 경험 같은 것을, 독자들이 이해하기 쉽도록 정돈해준&lt;/b&gt;, 말 그대로 &quot;정제된 지식의 결정체&quot; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아티클이 좋은 점 또 하나는, 제가 평소에 잘 생각해보지 못했거나 저와는 완전히 다른 관점의 의견에 대해서도 접할 수 있다는 점입니다. 각 아티클의 주된 관점, 의견, 생각이 맞고 틀리고를 떠나서, &lt;b&gt;지속적으로 외부 세상의 정보를 접하고 뇌에 신선한 자극을 줄 수 있다&lt;/b&gt;는 점에서 아티클은 좋은 툴이 될 수 있다고 생각합니다! 그리고 평소에 생각지도 못한 문제 해결 방법을 얻을 수도 있습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqQawj/btsPB3FtrwC/Y4wve0tKcN54I32Ri9aa50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqQawj/btsPB3FtrwC/Y4wve0tKcN54I32Ri9aa50/img.png&quot; data-alt=&quot;Korean FE Article 뉴스레터로 받은 아티클&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqQawj/btsPB3FtrwC/Y4wve0tKcN54I32Ri9aa50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqQawj%2FbtsPB3FtrwC%2FY4wve0tKcN54I32Ri9aa50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;48&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Korean FE Article 뉴스레터로 받은 아티클&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프론트엔드와 관련된 최신 아티클을 빠르게 받을 수 있도록 여러 뉴스레터를 구독해둔 상태입니다. 그리고 주로 아침 출근길에 메일함을 확인합니다. 출근 시간동안 읽을 수 있는 분량이면 바로 읽고, 생각보다 분량이 길다면 Chrome 앱의 추가탭으로 열어두고 미룹니다. 그리고 이렇게 미뤄둔 아티클들은 주말 스타벅스에서 커피를 홀짝이며 천천히 읽어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWVHyb/btsPBkAWelz/84qMTzNUKTmaw6ITdEIIMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWVHyb/btsPBkAWelz/84qMTzNUKTmaw6ITdEIIMK/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2214&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWVHyb/btsPBkAWelz/84qMTzNUKTmaw6ITdEIIMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWVHyb%2FbtsPBkAWelz%2F84qMTzNUKTmaw6ITdEIIMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2214&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H2tSJ/btsPzFUbVtX/pMriauTQfrj6kObwS7QHdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H2tSJ/btsPzFUbVtX/pMriauTQfrj6kObwS7QHdk/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2214&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H2tSJ/btsPzFUbVtX/pMriauTQfrj6kObwS7QHdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH2tSJ%2FbtsPzFUbVtX%2FpMriauTQfrj6kObwS7QHdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2214&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;항상 읽어야할 아티클로 넘쳐나는 크롬 탭..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DRZOY/btsPBE6YntC/aozHbgh7YvXvsEebUFJDUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DRZOY/btsPBE6YntC/aozHbgh7YvXvsEebUFJDUk/img.png&quot; data-alt=&quot;지금 스벅에서 글 쓰는 중&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DRZOY/btsPBE6YntC/aozHbgh7YvXvsEebUFJDUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDRZOY%2FbtsPBE6YntC%2FaozHbgh7YvXvsEebUFJDUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;424&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지금 스벅에서 글 쓰는 중&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;읽다가 센디 팀원들에게도 공유하고 싶은 아티클이라면 바로 슬랙 #share 채널에도 공유하고 있습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;1116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZebyD/btsPzZkC9kR/WXlUMk68fs3sltvb8OKc9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZebyD/btsPzZkC9kR/WXlUMk68fs3sltvb8OKc9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZebyD/btsPzZkC9kR/WXlUMk68fs3sltvb8OKc9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZebyD%2FbtsPzZkC9kR%2FWXlUMk68fs3sltvb8OKc9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;503&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;1116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 이미 많은 분들이 아티클을 구독해서 읽고 계시리라 생각합니다! 그래도 이번 기회에 스스로의 학습 방법을 정리해보고자 글로써 정리해보았습니다 :) 앞으로 저도 좋은 아티클을 작성할 수 있는 좋은 개발자가 되고 싶습니다. 아래에 제가 구독한 뉴스레터 목록을 남깁니다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구독한 뉴스레터&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. Korean FE Article&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;span&gt;&lt;span style=&quot;color: #1f1f1f;&quot; data-hovercard-owner-id=&quot;13&quot; data-hovercard-id=&quot;dev@ethansup.net&quot;&gt;&lt;span&gt;Ones To Watch Frontend&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;3. &lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;FE News&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;4. 토스 테크블로그 같은 기술 블로그 구독&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/625</guid>
      <comments>https://insengnewbie.tistory.com/625#entry625comment</comments>
      <pubDate>Sat, 26 Jul 2025 18:09:46 +0900</pubDate>
    </item>
    <item>
      <title>프론트? 그거 비전공자나 하는 거잖아: 추상화가 만든 착시</title>
      <link>https://insengnewbie.tistory.com/624</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0KnXl/btsPptlNhFy/jOpsKZKKZ4Mc3Qq6kooRxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0KnXl/btsPptlNhFy/jOpsKZKKZ4Mc3Qq6kooRxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0KnXl/btsPptlNhFy/jOpsKZKKZ4Mc3Qq6kooRxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0KnXl%2FbtsPptlNhFy%2FjOpsKZKKZ4Mc3Qq6kooRxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;649&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교 에브리타임에서 이런 게시물을 본 적이 있습니다. &amp;ldquo;웹 프론트엔드는 아무나 할 수 있다&amp;rdquo;거나 &amp;ldquo;리액트는 코딩 잘 모르는데도 금방 배우더라&amp;rdquo;는 글들이었죠. 온라인 커뮤니티에서도 종종 &amp;ldquo;진짜 개발은 백엔드&amp;rdquo;라는 댓글을 마주하곤 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C2g7V/btsPpvRsJpm/beNxILsN2IWWUGCiLUz2Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C2g7V/btsPpvRsJpm/beNxILsN2IWWUGCiLUz2Jk/img.png&quot; width=&quot;454&quot; height=&quot;101&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;151&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.6&quot; style=&quot;width: 49.0215%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C2g7V/btsPpvRsJpm/beNxILsN2IWWUGCiLUz2Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC2g7V%2FbtsPpvRsJpm%2FbeNxILsN2IWWUGCiLUz2Jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be6jqj/btsPpX7Vw44/mFWLukY0s7fQpUBKsxhi70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be6jqj/btsPpX7Vw44/mFWLukY0s7fQpUBKsxhi70/img.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;151&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.8157%;&quot; data-widthpercent=&quot;50.4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be6jqj/btsPpX7Vw44/mFWLukY0s7fQpUBKsxhi70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe6jqj%2FbtsPpX7Vw44%2FmFWLukY0s7fQpUBKsxhi70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 그냥 웃어넘겼는데, 비슷한 이야기가 반복될수록 마음 한편이 씁쓸해지더라고요. 프론트엔드를 전공하거나 새로운 언어를 배우고 있는 사람 입장에선 정말 억울한 이야기였으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때마다 주변에서 &amp;ldquo;그럼 프론트엔드가 뭐가 어려운데? 백엔드처럼 자료구조를 쓰는 것도 아니고, 요즘에는 AI로 딸깍 만들어낼 수도 있잖아?&amp;rdquo; 라는 질문을 받았습니다. 사이드 프로젝트 정도라면 AI로 간단히 화면을 뚝딱 만들어도 큰 문제가 없다고 생각해요. 그런데 제가 실제 업무에서 마주하는 문제들은 훨씬 복잡했습니다. 레이아웃 최적화나 복잡한 상태 관리, 접근성 보장, 성능 튜닝, 다양한 브라우저 호환성 등은 AI로는 단번에 해결하기 어려운 과제들이니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 입 밖으로 꺼내 설명하려 하면 뭐가 그렇게 어려운지 잘 정리가 안 되었는데, 이번 기회에 글로 한 번 정리해보려고 합니다. 이 글에서는 제가 목격한 사례를 출발점으로, &amp;lsquo;웹 프론트엔드는 왜 과소평가받을까?&amp;rsquo;, 그리고 &amp;lsquo;프론트엔드가 실제로 왜 어려운가?&amp;rsquo;를 사회문화적&amp;middot;기술적&amp;middot;심리적 배경을 통해 차근차근 살펴보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고백&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저는 CS 전공자로, 3학년이 되기 전까지 저도 속으로 &lt;b&gt;&amp;lsquo;프론트엔드는 비전공자나 하는 거지&amp;rsquo;&lt;/b&gt;라는 생각을 하고 있었습니다. 그런데 2023년 초, 크래프톤 정글 부트캠프에서 프로젝트를 진행할 때 일이 달라졌어요. 팀원들이 다들 백엔드를 하고 싶어 해서 프론트엔드 역할을 맡았는데, 사실 싫지는 않았습니다. &lt;b&gt;&amp;lsquo;진짜 개발자라면 직무에 상관없이 어떤 문제든 해결할 수 있어야 한다&amp;rsquo;&lt;/b&gt;는 생각이 있었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 2달 동안 프론트엔드로 프로젝트를 진행해 보니 적성에도 딱 맞았고, 오히려 재미있었습니다. 바로바로 결과물을 확인할 수 있는 것뿐만 아니라, 사용자의 동작을 UX적으로 유도하는 과정과, 깊이 파고들수록 더 어려워지는 기술적 도전이 매력적으로 다가왔습니다. 그 경험을 계기로 프론트엔드를 진로로 결정했고, 결국 현재 센디에서 1년차 프론트엔드 엔지니어로 일하고 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프론트엔드가 쉽다고 여겨지는 이유?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 프론트엔드가 다른 기술 직군에 비해 기술력이 부족하다고 여겨지는 걸까요? 여기에는 여러 기술적이거나 사회문화적 배경이 작용했다고 생각합니다. 아래는 제가 관찰한 요인을 정리한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사회문화적 배경&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2115&quot; data-origin-height=&quot;1199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ImU2K/btsPqWUIdoN/lUKhBWOwC67poygj5xp0x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ImU2K/btsPqWUIdoN/lUKhBWOwC67poygj5xp0x1/img.png&quot; data-alt=&quot;코로나 당시 엄청나게 홍보하는 부트캠프들..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ImU2K/btsPqWUIdoN/lUKhBWOwC67poygj5xp0x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FImU2K%2FbtsPqWUIdoN%2FlUKhBWOwC67poygj5xp0x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2115&quot; height=&quot;1199&quot; data-origin-width=&quot;2115&quot; data-origin-height=&quot;1199&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코로나 당시 엄청나게 홍보하는 부트캠프들..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코로나19 시기 국비지원 부트캠프가 우후죽순 생겨나면서, 비전공자들이 대거 웹 프론트엔드 개발로 넘어왔습니다. 이 과정에서 &lt;i&gt;&lt;b&gt;&amp;lsquo;프론트엔드 = 비전공자&amp;rsquo;&lt;/b&gt;&lt;/i&gt;라는 고정관념이 생겨났고, 자연스레 CS 전공자들 사이에서는 &amp;ldquo;진짜 개발은 백엔드&amp;rdquo;라는 선입견이 강화됐습니다. 결국 프론트엔드는 처음부터 기술력이 상대적으로 낮다는 이미지가 덧씌워진 채 자리 잡게 된 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드는 HTML과 CSS 몇 줄만 알면 눈에 보이는 결과물을 금방 만들 수 있습니다. 이런 &amp;lsquo;표면적 단순함&amp;rsquo; 때문에 복잡한 알고리즘이나 인프라를 다루는 백엔드와 비교해 기술 난이도가 낮아 보이기 쉽습니다. 그러나 React나 Vue 같은 프레임워크는 내부적으로 상태 관리, 가상 DOM, 빌드 파이프라인 등 복잡한 로직을 추상화해 숨기기 때문에, 오히려 심오한 기술이 감춰진 채 &amp;ldquo;쉬워 보이는&amp;rdquo; 상황이 되는 거죠. 여기에 워낙 다양한 빌드 도구와 브라우저 호환성 이슈까지 얽히다 보니, 알고 보면 깊게 파고들어야 할 영역이 훨씬 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;심리적&amp;middot;인지적 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입문 장벽이 낮아 배우자마자 바로 결과를 확인할 수 있는 프론트엔드에서는 더닝크루거 효과가 자주 나타납니다. &amp;ldquo;아, 나도 금방 이 정도는 할 수 있겠네?&amp;rdquo; 하고 스스로 전문가라 착각하기 쉽죠. 하지만 경험이 쌓일수록 보이지 않는 상태 관리나 렌더링 최적화, 접근성 같은 개념의 중요성을 깨닫게 됩니다. 자신감만 높다는 것은 오히려 무지를 드러내는 신호가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;더닝크루거 효과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xuQxR/btsPqasrmBz/mFcMunrqTz89ZWX5mexx7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xuQxR/btsPqasrmBz/mFcMunrqTz89ZWX5mexx7k/img.png&quot; data-alt=&quot;밈으로 자주 보는 이 짤의 원형&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xuQxR/btsPqasrmBz/mFcMunrqTz89ZWX5mexx7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxuQxR%2FbtsPqasrmBz%2FmFcMunrqTz89ZWX5mexx7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;345&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;밈으로 자주 보는 이 짤의 원형&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더닝크루거 효과는 무지한 사람이 자신의 능력을 과대평가하는 현상을 설명하는 심리학 이론입니다. 즉, &amp;lsquo;어떤 분야에 대해 모르는 상태&amp;rsquo;일 때는 그 사실조차 인지하지 못해 자신감이 높아지는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;아, 이 정도면 나도 잘할 수 있겠는데?&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실제로 이 단계에서 학습자들은 간단한 개념 몇 가지만 알고 있어도 스스로를 &amp;lsquo;전문가&amp;rsquo;로 여기기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 더 많이 배우고 경험을 쌓을수록 &amp;lsquo;자신의 무지&amp;rsquo;를 깨닫고 오히려 자신감이 내려갑니다. 결국 진짜 숙련자는 자신의 한계를 잘 알고, 필요한 만큼 배우고 또 배우는 자세를 갖추게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &amp;lsquo;자신감만 높다&amp;rsquo;는 것은 오히려 무지를 드러내는 신호일 수 있습니다. 프론트엔드 관련 대화에서도, 몇 가지 간단한 코드와 결과 확인만으로 &amp;lsquo;내가 다 알았다&amp;rsquo;고 생각하기 쉬운데, 이것이 바로 더닝크루거 효과가 작용하는 대표적 사례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트가 쉽다고 느껴지는 이유?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드, 특히 React가 &amp;ldquo;쉽다&amp;rdquo;고 느껴지는 데에는 여러 구조적&amp;middot;심리적 이유가 있다고 생각합니다. 크게 다섯 가지 관점에서 살펴볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 추상화된 선언형 API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 Virtual DOM과 컴포넌트 단위의 선언적(Declarative) 렌더링 모델을 제공합니다. 전통적인 DOM 조작 방식&amp;mdash;appendChild, removeChild, 이벤트 위임 등을 직접 다루지 않아도 되죠. 예를 들어&lt;/p&gt;
&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;return &amp;lt;Button onClick={handleClick}&amp;gt;클릭&amp;lt;/Button&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 선언식 코드를 쓰면, React가 내부에서 &amp;lsquo;어떻게&amp;rsquo; 화면에 그려주고 업데이트할지를 알아서 처리해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 React는 &amp;ldquo;UI = 함수(state)&amp;rdquo;라는 단순한 모델을 제공해 진입 장벽을 낮추지만, 그 뒤에는 동시성 렌더링(concurrent rendering), 스케줄링(prioritization), hydration, code splitting, 서버 경계(Server Components) 같은 레이어가 겹쳐 있어, 추상화가 초보자에게 난이도를 가린 채 &lt;b&gt;&amp;lsquo;착시&amp;rsquo;&lt;/b&gt;를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 강력한 개발자 경험 (DX)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 생태계는 코드 변경 사항을 즉시 확인할 수 있는 Hot Reloading을 기본으로 제공합니다. 여기에 Vite, Next.js 같은 스캐폴딩 도구가 번들링&amp;middot;트랜스파일&amp;middot;라이브 리로드 등 초기 설정을 전부 처리해 주니, 환경 구성 자체가 매우 간단하게 느껴집니다. 서버나 배포 파이프라인을 일일이 신경 써야 하는 백엔드 개발과 비교하면, React 개발은 단연코 &amp;lsquo;손쉬운 시작점&amp;rsquo;을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 방대한 생태계와 학습 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 관련 학습 자료는 정말 방대합니다. 공식 문서와 튜토리얼을 비롯해 부트캠프 커리큘럼, 유튜브 강의, 깃허브 예제 등이 넘쳐나죠. 게다가 MUI, Ant Design, Chakra UI 같은 완성도 높은 UI 컴포넌트 라이브러리를 붙여 쓰면 버튼 하나, 테이블 하나를 구현할 때도 직접 CSS나 레이아웃을 고민할 필요가 거의 없습니다. &amp;ldquo;이 라이브러리만 쓰면 금방 그럴듯한 화면이 나온다&amp;rdquo;는 성공 경험이 쌓일수록, 학습 과정이 더 쉽고 재미있어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 단일 언어로의 단순성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발은 Go, Java, Python 같은 여러 언어와 데이터베이스, 인증, 캐시, 큐, 배포 파이프라인, 보안 등을 동시에 고려해야 합니다. 반면 React는 JavaScript(또는 TypeScript), HTML, CSS만 알면 &amp;lsquo;화면&amp;rsquo;이 만들어집니다. 입문자가 한 번에 두세 가지 기술을 동시에 배우기 부담스러워할 일이 적으니, 자연스럽게 &amp;lsquo;쉽다&amp;rsquo;는 인식이 자리 잡는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 드러나지 않는 복잡성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입문 튜토리얼 단계에서는 잘 다루어지지 않는 ARIA 접근성, 국제화(i18n), 성능 튜닝, SSR/SSG, 코드 스플리팅, 타입 안전성(TypeScript), 테스트, 보안(CSRF/XSS) 이슈 등 핵심 난제들이 많습니다. 하지만 이러한 영역은 초보자가 쉽게 마주치지 못하도록 가려져 있기 때문에 &amp;lsquo;React = 화면 그려 주는 라이브러리&amp;rsquo;라는 편협한 인식이 생겨나죠. 실제 프로덕션 단계에서는 이 모든 것을 고민해야 하니, 깊이 파고들면 백엔드 못지않은 전문성이 필요한 분야라는 사실을 깨닫게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 제 경험과 사회문화적&amp;middot;기술적&amp;middot;심리적 배경, 그리고 협업&amp;middot;비기능 요구사항 관점에서 프론트엔드가 왜 무시받아 왔는지 살펴봤습니다. 이를 종합해 보면, 프론트엔드가 어려운 이유는 크게 다음 세 가지로 정리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;b&gt;숨겨진 복잡성&lt;/b&gt;입니다. 선언적 API나 Hot Reloading 같은 편의 기능 뒤에는 상태 관리, 렌더링 최적화, 접근성(ARIA), 국제화(i18n), 브라우저별 호환성 등 수많은 고려사항이 숨어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;심리적 인지 장벽&lt;/b&gt;입니다. 입문 장벽이 낮아 빠르게 결과가 보이지만, 더닝크루거 효과로 자신의 부족함을 깨닫기 어렵고, 깊이 있는 개념을 학습하기 전까지는 진정한 난이도를 체감하기 힘듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, &lt;b&gt;협업과 비기능 요구사항&lt;/b&gt;입니다. 디자이너&amp;middot;PM&amp;middot;백엔드 엔지니어와의 원활한 소통, 성능 최적화, SEO, 보안 같은 UI 너머의 문제들을 동시에 해결해야 하므로, 단순히 &amp;lsquo;화면을 그리는 일&amp;rsquo;이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 프론트엔드는 외견상 단순해 보여도, 실제로는 눈에 보이지 않는 복잡한 과제들을 해결해야 하는 분야입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 요즘과 같이 AI 주도 개발, 바이브 코딩이 메인스트림인 세상에서 프론트엔드, 백엔드, ML 뭐가 더 어렵네마네 하는 것은 어린 생각으로 들립니다. 물리, 영어, 수학, 역사처럼 말 그대로 &quot;분야&quot;가 다른 것이니까요. 어려움의 정도를 비교할 수 있는 것이 아니라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvY0SV/btsPp04Fpf9/HMGom6Ec0AIQjJIWXXQgNK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvY0SV/btsPp04Fpf9/HMGom6Ec0AIQjJIWXXQgNK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvY0SV/btsPp04Fpf9/HMGom6Ec0AIQjJIWXXQgNK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvY0SV%2FbtsPp04Fpf9%2FHMGom6Ec0AIQjJIWXXQgNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;291&quot; height=&quot;438&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 &amp;ldquo;어느 쪽이 더 어렵다&amp;rdquo;는 논쟁보다는 &amp;ldquo;각 도메인에서 추상화가 무엇을 감추고 있는지&amp;rdquo;를 발견하는 것이 우리의 성장에 더 생산적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여러분이 겪은 &amp;lsquo;추상화 뒤의 낭떠러지&amp;rsquo;는 무엇이었나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <category>react</category>
      <category>추상화</category>
      <category>프론트엔드</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/624</guid>
      <comments>https://insengnewbie.tistory.com/624#entry624comment</comments>
      <pubDate>Sun, 20 Jul 2025 19:00:29 +0900</pubDate>
    </item>
    <item>
      <title>&amp;quot;대충 좋으니까&amp;quot; 쓰던 커스텀 훅, 이제는 설명할 수 있습니다</title>
      <link>https://insengnewbie.tistory.com/623</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhTqM/btsOBFzCvk8/PImA0gcLgAqpDV6crV8Hi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhTqM/btsOBFzCvk8/PImA0gcLgAqpDV6crV8Hi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhTqM/btsOBFzCvk8/PImA0gcLgAqpDV6crV8Hi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrhTqM%2FbtsOBFzCvk8%2FPImA0gcLgAqpDV6crV8Hi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;346&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;커스텀 훅이 무엇인가요?&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 프론트엔드 개발자 면접 단골 질문입니다. 저는 면접 당시에 GPT가 생성해준 답변을 아래와 같이 설명하는 걸로 넘어갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅은 React에서 상태 로직을 재사용하기 위한 기능입니다. 함수 컴포넌트 내에서 useState, useEffect 등 React Hook을 사용하여 로직을 구현하고, 필요한 경우 반환 값으로 상태나 함수를 제공하여 다른 컴포넌트에서 재사용할 수 있도록 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 React로 클라이언트 로직을 구현할 때 습관적으로 useToggle, useModal, useAnalytics와 같은 커스텀 훅(Custom Hook)을 만들어 사용해왔습니다. 처음에는 단순히 반복되는 코드를 여러 번 입력하는 것이 번거로워 재사용성을 높이고자 커스텀 훅을 만들기 시작했습니다. 하지만 프론트엔드 개발자로서 일한지 1년이 지난 지금도 '커스텀 훅이 무엇인가?'라는 질문에 위와 같은 기계적인 답변 밖에 떠오르지 않았습니다. 생각해보면 사용할 줄만 알 뿐, 커스텀 훅이란 것에 대해 깊게 공부해본 적이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 많은 프론트엔드 개발자분들이 저처럼 커스텀 훅이 무엇이고, 무엇을 할 수 있고, 왜, 그리고 어떻게 좋은지 명확히 설명하기 어려워하면서도 무의식적으로 사용하고 계실 것이라 생각합니다. 이번 기회를 통해 커스텀 훅에 대해 깊이 탐구하고 학습하며, 그 본질을 다시 한번 되짚어보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 훅이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅에 대한 개념은 많은 분들이 알고 계시는 것과 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 훅(Custom Hook)&lt;/b&gt;은 React의 기본 훅(useState, useEffect, useContext 등)을 사용하여 상태 관련 로직(stateful logic)을 재사용 가능한 함수로 만드는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 공식 문서에서는 커스텀 훅을 다음과 같이 정의합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;커스텀 훅은 이름이 use로 시작하는 JavaScript 함수입니다. 이 함수는 다른 훅을 호출할 수 있습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;'로직의 재사용'&lt;/b&gt;에 있습니다. 컴포넌트의 기능이 복잡해지면 여러 컴포넌트에서 비슷한 로직(예: 데이터 fetching, 폼 상태 관리, 이벤트 리스너 등록)이 반복적으로 나타날 수 있습니다. 이때 커스텀 훅을 사용하면 해당 로직을 하나의 함수로 추출하여 여러 컴포넌트에서 간결하게 호출하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;훅은 왜 &quot;훅&quot;일까?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBQMsO/btsOCfUJwku/loGg42xx2fwrzjYkHZi1g0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBQMsO/btsOCfUJwku/loGg42xx2fwrzjYkHZi1g0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBQMsO/btsOCfUJwku/loGg42xx2fwrzjYkHZi1g0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBQMsO%2FbtsOCfUJwku%2FloGg42xx2fwrzjYkHZi1g0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;396&quot; height=&quot;264&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훅은 왜 &quot;훅(Hook)&quot;이라는 이름이 붙었을까요? 결론부터 보면, 함수 컴포넌트가 React의 핵심 기능(state, lifecycle 등)에 &quot;갈고리(Hook)를 걸어&quot; 필요한 기능을 가져와 사용한다는 의미에서 '훅'이라는 이름이 붙었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;훅이 없던 시절: 클래스 컴포넌트의 시대&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 React에서 상태(state)를 관리하거나, 컴포넌트가 생성/소멸할 때 특정 작업을 수행하는 생명주기(lifecycle) 기능을 사용하려면 반드시 &lt;b&gt;클래스 컴포넌트(Class Component)&lt;/b&gt;를 사용해야 했습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }; // state는 클래스 내부에서만 선언 가능
  }

  componentDidMount() { // 생명주기 메서드
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() { // 생명주기 메서드
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;You clicked {this.state.count} times&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; this.setState({ count: this.state.count + 1 })}&amp;gt;
          Click me
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 함수 컴포넌트(Function Component)는 그저 props를 받아 UI를 그리는 역할만 하는 'Stateless' (상태가 없는) 컴포넌트였습니다. 상태나 생명주기 같은 React의 핵심 기능에 접근할 방법이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot;훅(Hook)&quot;의 등장: 함수 컴포넌트의 혁명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 팀은 클래스 컴포넌트의 복잡성(예: this 키워드의 혼란, 로직 재사용의 어려움)을 해결하고, 더 간결한 함수 컴포넌트에서 React의 모든 기능을 사용할 수 있게 하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 탄생한 것이 바로 &lt;b&gt;'훅(Hook)'&lt;/b&gt;입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dh2xTh/btsOBgtj7fK/OWH6nW6zkmkTGewekKYRyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dh2xTh/btsOBgtj7fK/OWH6nW6zkmkTGewekKYRyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dh2xTh/btsOBgtj7fK/OWH6nW6zkmkTGewekKYRyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdh2xTh%2FbtsOBgtj7fK%2FOWH6nW6zkmkTGewekKYRyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;424&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState, useEffect와 같은 훅 함수들은 이름 그대로, 평범했던 함수 컴포넌트에게 React의 보이지 않는 내부 시스템에 '갈고리를 걸 수 있는(hook into)' 능력을 부여했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useState: 함수 컴포넌트가 React의 상태(state) 관리 시스템에 갈고리를 걸어 상태를 가질 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;useEffect: 함수 컴포넌트가 React의 생명주기(lifecycle) 시스템에 갈고리를 걸어 컴포넌트의 생성, 업데이트, 소멸 시점에 코드를 실행할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;useContext: 함수 컴포넌트가 React의 컨텍스트(Context) 시스템에 갈고리를 걸어 전역 데이터를 쉽게 가져올 수 있게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 공식 문서에서도 훅을 소개하며 다음과 같이 설명합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Hooks are functions that let you 'hook into' React state and lifecycle features from function components.&quot; (&quot;훅은 함수 컴포넌트에서 React의 상태와 생명주기 기능에 '연결(hook into)'할 수 있게 해주는 함수입니다.&quot;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 '훅'이라는 이름은 단순히 특별한 함수를 지칭하는 것을 넘어, 기능이 제한적이었던 함수 컴포넌트가 React의 강력한 핵심 기능들을 직접 가져와 사용할 수 있게 만드는 행위와 개념 그 자체를 매우 직관적으로 표현한 것이라고 할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 훅의 라이프사이클&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅은 독립적인 라이프사이클을 갖지 않습니다. 훅은 그것을 사용하는 컴포넌트의 라이프사이클에 완전히 의존합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mount: 컴포넌트가 처음 렌더링될 때, 해당 컴포넌트 내부에서 사용된 훅들도 함께 호출됩니다. useState는 초기 상태를 설정하고, useEffect는 첫 렌더링 이후에 콜백 함수를 실행합니다.&lt;/li&gt;
&lt;li&gt;Update: 컴포넌트가 리렌더링될 때마다 훅들도 다시 호출됩니다. useState는 최신 상태 값을 반환하고, useEffect는 의존성 배열(dependency array)의 변화에 따라 콜백 함수를 재실행할지 결정합니다.&lt;/li&gt;
&lt;li&gt;Unmount: 컴포넌트가 화면에서 사라질 때, useEffect의 반환 함수(cleanup function)가 호출되어 정리 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 커스텀 훅은 컴포넌트의 일부처럼 동작하며, 상태와 사이드 이펙트를 컴포넌트에 '연결(hook into)'하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 훅의 장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;재사용성 (Reusability)&lt;/b&gt;: 가장 큰 장점입니다. 여러 컴포넌트에서 반복되는 로직을 하나의 훅으로 만들어두면, 필요할 때마다 손쉽게 가져다 쓸 수 있습니다. 이는 코드 중복을 줄이고 개발 생산성을 크게 향상시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관심사의 분리 (Separation of Concerns)&lt;/b&gt;: 컴포넌트는 UI를 렌더링하는 역할에 집중하고, 복잡한 상태 관리나 비동기 처리, 사이드 이펙트 등의 로직은 커스텀 훅으로 분리할 수 있습니다. 이를 통해 코드가 훨씬 깔끔해지고 이해하기 쉬워집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가독성 및 유지보수성 향상&lt;/b&gt;: 로직이 분리되면 컴포넌트 코드는 간결해집니다. useUserData처럼 의미 있는 이름의 훅을 사용하는 것만으로도 &quot;이 컴포넌트는 사용자 데이터를 사용하는구나&quot;라고 쉽게 파악할 수 있습니다. 이는 코드의 가독성을 높이고, 추후 로직 수정이 필요할 때 해당 훅만 수정하면 되므로 유지보수 또한 용이해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상화 (Abstraction)&lt;/b&gt;: 복잡한 로직의 내부 구현을 숨기고, 개발자는 훅이 반환하는 값(상태, 함수 등)에만 집중하면 됩니다. 예를 들어 useFetch 훅을 사용한다면, 내부적으로 axios를 쓰는지 fetch API를 쓰는지는 중요하지 않습니다. 그저 데이터를 가져오는 기능에만 집중할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 훅으로 할 수 있는 일!&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI 상태는 훅으로 얼마나 간단해질까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 true/false 토글 상태나 모달의 열림/닫힘 상태 관리는 매우 흔한 UI 로직입니다. 커스텀 훅을 사용하면 이를 매우 간단하게 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { useState, useCallback } from 'react';

export function useToggle(
  initialState: boolean = false,
): [boolean, () =&amp;gt; void] {
  const [state, setState] = useState(initialState);

  const toggle = useCallback(() =&amp;gt; {
    setState(prev =&amp;gt; !prev);
  }, []);

  return [state, toggle] as const;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어떤 컴포넌트에서든 &lt;code&gt;const [isToggled, toggle] = useToggle(false);&lt;/code&gt; 한 줄만으로 토글 상태와 그 상태를 변경하는 함수를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비동기 작업은 훅으로 어떻게 깔끔해질까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 fetching과 같은 비동기 작업은 status, data, error라는 3가지 상태를 관리해야 해서 코드가 복잡해지기 쉽습니다. useAsync와 같은 훅을 만들면 이를 체계적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;type AsyncState&amp;lt;T&amp;gt; =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: unknown };

export function useAsync&amp;lt;T&amp;gt;(
  fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;,
  deps: DependencyList = [],
) {
  const [state, setState] = useState&amp;lt;AsyncState&amp;lt;T&amp;gt;&amp;gt;({ status: 'idle' });

  useEffect(() =&amp;gt; {
    const abort = new AbortController();
    const run = async () =&amp;gt; {
      setState({ status: 'loading' });
      try {
        const data = await fn();
        if (!abort.signal.aborted) setState({ status: 'success', data });
      } catch (e) {
        if (!abort.signal.aborted) setState({ status: 'error', error: e });
      }
    };
    run();
    return () =&amp;gt; abort.abort();
  }, deps);

  return state;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에서는 &lt;code&gt;const { status, error, data } = useAsync(fetchUsers);&lt;/code&gt; 와 같이 선언적으로 비동기 작업을 처리할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저 이벤트&amp;middot;디바이스 연동도 훅으로 가능할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 크기 변화, 스크롤 위치, 온라인 상태 등 브라우저나 디바이스의 상태와 직접 상호작용하는 로직은 useEffect를 사용한 이벤트 리스너의 등록 및 해제가 필수적입니다. 이러한 '설정'과 '정리' 로직을 커스텀 훅으로 캡슐화하면 재사용성이 극대화되고 컴포넌트의 복잡도는 크게 낮아집니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react';

/**
 * 브라우저 창의 현재 크기를 추적하는 커스텀 훅
 * @returns {{width: number, height: number}} - 현재 창의 가로, 세로 크기
 */
export function useWindowSize() {
  // 1. 창 크기를 저장할 state를 생성합니다.
  //    초기값은 undefined이며, 클라이언트 사이드에서만 값을 계산합니다.
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });

  useEffect(() =&amp;gt; {
    // 2. resize 이벤트를 처리할 핸들러 함수를 정의합니다.
    const handleResize = () =&amp;gt; {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    // 3. 컴포넌트가 마운트될 때 resize 이벤트 리스너를 추가하고,
    //    초기 사이즈를 한 번 설정해 줍니다.
    window.addEventListener('resize', handleResize);
    handleResize(); // 초기 사이즈 설정을 위해 한 번 호출

    // 4. 컴포넌트가 언마운트될 때 이벤트 리스너를 제거합니다. (매우 중요!)
    //    이를 'cleanup' 함수라고 부르며, 메모리 누수를 방지합니다.
    return () =&amp;gt; window.removeEventListener('resize', handleResize);
  }, []); // 의존성 배열이 비어 있으므로, 마운트 시 1회만 실행됩니다.

  return windowSize;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 useWindowSize 훅을 사용하면 이벤트 리스너를 직접 등록하거나 해제하는 복잡한 과정에 대해 전혀 신경 쓸 필요가 없습니다. 그저 필요한 값(width, height)을 선언적으로 가져다 쓰기만 하면 됩니다. 이와 같은 방식으로 useScroll(스크롤 위치 추적), useOnlineStatus(네트워크 상태 감지), useClipboard(클립보드 복사) 등 수많은 브라우저 API 관련 로직을 명료하고 재사용 가능한 훅으로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 훅과 의존성 주입(DI)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baLBMR/btsOBixXx43/1kgyXePAoQSJ7gdtkp09hk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baLBMR/btsOBixXx43/1kgyXePAoQSJ7gdtkp09hk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baLBMR/btsOBixXx43/1kgyXePAoQSJ7gdtkp09hk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaLBMR%2FbtsOBixXx43%2F1kgyXePAoQSJ7gdtkp09hk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;333&quot; height=&quot;290&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 더 나아가 커스텀 훅은 &lt;b&gt;의존성 주입(Dependency Injection)&lt;/b&gt;을 통해 유연성을 극대화합니다. 훅이 필요로 하는 기능(의존성)을 외부에서 인자(argument)로 전달받는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react';

/** 비동기 함수를 받아 데이터를 반환하는 커스텀 훅 */
export function useFetch&amp;lt;T&amp;gt;(fetcher: () =&amp;gt; Promise&amp;lt;T&amp;gt;): T | null {
  const [data, setData] = useState&amp;lt;T | null&amp;gt;(null);

  useEffect(() =&amp;gt; {
    let cancelled = false;

    fetcher()
      .then(result =&amp;gt; {
        if (!cancelled) setData(result);
      })
      .catch(console.error); // 필요하다면 에러 상태도 추가하세요.

    return () =&amp;gt; {
      cancelled = true; // 언마운트 후 setState 방지
    };
  }, [fetcher]);

  return data;
}

// 사용 예시
import { useFetch } from './useFetch';
import { axiosFetcher, swrFetcher} from './api/fetchers';

const ComponentA: React.FC = () =&amp;gt; {
  const users = useFetch&amp;lt;User[]&amp;gt;(axiosFetcher);

  return (
    &amp;lt;ul&amp;gt;
      {users?.map(u =&amp;gt; (
        &amp;lt;li key={u.id}&amp;gt;{u.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};

const ComponentB: React.FC = () =&amp;gt; {
  const posts = useFetch&amp;lt;Post[]&amp;gt;(swrFetcher);

  return (
    &amp;lt;ul&amp;gt;
      {posts?.map(p =&amp;gt; (
        &amp;lt;li key={p.id}&amp;gt;{p.title}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 useFetch 훅은 어떻게 데이터를 가져올 것인지에 대한 책임을 외부로 위임합니다. 덕분에 이 훅은 axios 뿐만 아니라 브라우저 내장 fetch, SWR, React-Query 등 어떤 데이터 fetching 방식과도 함께 동작할 수 있는 높은 유연성과 재사용성을 확보하게 됩니다. 이는 특히 테스트 시 실제 API 대신 가짜(Mock) API 함수를 주입하여 훅을 독립적으로 검증할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레이어의 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅을 활용하면 UI 로직, 비즈니스 로직, 데이터 fetching 로직 등 코드의 &lt;b&gt;레이어(Layer)&lt;/b&gt;를 명확하게 분리할 수 있습니다. 이는 애플리케이션의 아키텍처를 견고하게 만드는 핵심적인 역할을 합니다. 이 주제는 내용이 깊어 다음 아티클에서 더 자세히 다루도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여러가지 커스텀 훅 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useToggle&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useCallback } from 'react';

/**
 * boolean 값을 토글하는 커스텀 훅
 * @param {boolean} initialState - 초기 상태값 (기본값: false)
 * @returns {[boolean, () =&amp;gt; void]} - [현재 상태, 토글 함수]
 */
export function useToggle(initialState = false) {
  // 1. useState로 토글 상태를 관리합니다.
  const [state, setState] = useState(initialState);

  // 2. useCallback을 사용하여 함수를 메모이제이션합니다.
  //    컴포넌트가 리렌더링되어도 toggle 함수는 재생성되지 않아 성능 최적화에 도움이 됩니다.
  const toggle = useCallback(() =&amp;gt; {
    setState(prev =&amp;gt; !prev);
  }, []);

  // 3. 현재 상태(state)와 상태를 변경하는 함수(toggle)를 배열 형태로 반환합니다.
  return [state, toggle] as const;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useAsync&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;type AsyncState&amp;lt;T&amp;gt; =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: unknown };

export function useAsync&amp;lt;T&amp;gt;(
  fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;,
  deps: DependencyList = [],
) {
  const [state, setState] = useState&amp;lt;AsyncState&amp;lt;T&amp;gt;&amp;gt;({ status: 'idle' });

  useEffect(() =&amp;gt; {
    const abort = new AbortController();
    const run = async () =&amp;gt; {
      setState({ status: 'loading' });
      try {
        const data = await fn();
        if (!abort.signal.aborted) setState({ status: 'success', data });
      } catch (e) {
        if (!abort.signal.aborted) setState({ status: 'error', error: e });
      }
    };
    run();
    return () =&amp;gt; abort.abort();
  }, deps); // deps 내부에 fn이나 필요한 파라미터 포함

  return state;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useAsyncEffect&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;export function useAsyncEffect(
  effect: () =&amp;gt; Promise&amp;lt;void | (() =&amp;gt; void)&amp;gt;,
  deps?: DependencyList,
) {
  useEffect(() =&amp;gt; {
    let cleanup: (() =&amp;gt; void) | void;

    effect().then(result =&amp;gt; {
      cleanup = result;
    });

    return () =&amp;gt; {
      cleanup?.();
    };
  }, deps);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비대해진 훅 관리 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅도 하나의 기능에 너무 많은 책임을 지게 되면 비대해지고 유지보수가 어려워집니다. 특히나 하나의 페이지의 모든 로직을 처리하는 usePage 라는 훅이 있다면 더욱 곤란할 것입니다. 이를 방지하기 위한 전략은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쪼개기 (단일 책임 원칙): 하나의 훅은 하나의 기능만 잘하도록 만듭니다. 예를 들어, 사용자 정보를 가져오고 수정하고 삭제하는 기능을 모두 담은 useUser 훅이 있다면, 이를 useFetchUser, useUpdateUser, useDeleteUser 등으로 분리하는 것을 고려할 수 있습니다.&lt;/li&gt;
&lt;li&gt;데이터 계층화: 훅을 조합하여 더 고수준의 훅을 만들 수 있습니다. 예를 들어, useFetch라는 범용 fetching 훅을 먼저 만들고, 이 훅을 사용하여 useFetchUser, useFetchPosts와 같은 구체적인 데이터 소스를 다루는 훅을 만드는 방식입니다. 이를 통해 로직의 재사용성을 극대화하고 계층을 명확히 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 저수준 훅 (범용)
const useFetch = (url) =&amp;gt; { /* ... fetching 로직 ... */ };

// 고수준 훅 (구체적)
const useFetchUser = (userId) =&amp;gt; {
  return useFetch(`/api/users/${userId}`);
};

const useFetchPosts = (userId) =&amp;gt; {
    return useFetch(`/api/posts?userId=${userId}`);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;많은 프론트엔드 개발자들이 커스텀 훅을 잘 쓰면서도 설명하기 힘들어하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 커스텀 훅이 가진 &lt;b&gt;'추상성'&lt;/b&gt;과 '선언적 프로그래밍' 패러다임 때문이라고 생각합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;직관적이지만 설명은 어려운 추상성&lt;/b&gt;: useToggle은 &quot;토글 기능을 제공한다&quot;는 것을 직관적으로 알 수 있습니다. 하지만 그 내부에서 useState와 useCallback이 어떻게 동작하고, 왜 useCallback으로 감싸야 하는지, 컴포넌트 리렌더링 시 어떤 일이 일어나는지를 단계별로 설명하는 것은 다른 차원의 문제입니다. 우리는 잘 만들어진 도구를 그 원리를 몰라도 잘 사용하는 것과 비슷합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선언적 사고방식&lt;/b&gt;: 훅은 &quot;무엇을 할 것인가&quot;를 선언하는 방식에 가깝습니다. &quot;로딩 중이면 스피너를 보여주고, 데이터가 있으면 목록을 보여준다&quot;와 같이 상태에 따른 결과만 기술하면 됩니다. &quot;어떻게&quot; 데이터를 가져오고 상태를 변경하는지에 대한 절차적 로직은 훅 내부에 숨겨져 있습니다. 이 선언적인 방식이 매우 편리하기 때문에, 그 내부의 명령형 로직을 굳이 파헤쳐 보지 않게 되는 경향이 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 커스텀 훅은 그 자체로 매우 효율적인 '도구'이기 때문에, 내부 동작 원리를 깊이 이해하지 못해도 당장의 생산성에는 큰 문제가 없어 보이기 때문일 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 커스텀 훅의 개념부터 장점, 그리고 효과적인 관리 전략까지 살펴보았습니다. 커스텀 훅은 단순히 코드를 재사용하는 것을 넘어, React 애플리케이션을 더 선언적이고, 견고하며, 유지보수하기 쉽게 만드는 핵심적인 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무의식적으로 사용하던 코드에 '왜?'라는 질문을 던지고 그 본질을 탐구하는 과정은 개발자로서 한 단계 성장하는 중요한 계기가 된다고 생각합니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 공식 문서 - 나만의 훅 만들기: &lt;a href=&quot;https://react.dev/learn/reusing-logic-with-custom-hooks&quot;&gt;https://react.dev/learn/reusing-logic-with-custom-hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;useHooks - A collection of modern, server-safe React hooks: &lt;a href=&quot;https://usehooks.com/&quot;&gt;https://usehooks.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/623</guid>
      <comments>https://insengnewbie.tistory.com/623#entry623comment</comments>
      <pubDate>Sat, 14 Jun 2025 20:30:19 +0900</pubDate>
    </item>
    <item>
      <title>말도 패션이다 &amp;ndash; TPO에 맞춰 더 똑똑하게 대화하기</title>
      <link>https://insengnewbie.tistory.com/622</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;머리말&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 문서는  유튜브 채널 미키피디아의 &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://youtu.be/mn3NV7vVIzM?si=Wo3vSchypu8qe4Hm&quot;&gt;&amp;lt;말 잘하는 사람들의 비결은 뭘까? with 조승연 작가&amp;gt;&lt;/a&gt; 유튜브 영상을 시청하며 필기한 메모를 체계적으로 재구성한 개인 학습 기록이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Part 1 |  &amp;lsquo;말 잘하기&amp;rsquo;의 재정의&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.1 &amp;lsquo;만능 화술&amp;rsquo;이라는 환상&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;옷차림과 동일하게, 말하기는 &lt;b&gt;TPO(&lt;/b&gt;&lt;span&gt;&lt;b&gt;상황&amp;middot;상대&amp;middot;목적)&lt;/b&gt;&lt;/span&gt;에 따라 달라져야 한다.&lt;/li&gt;
&lt;li&gt;모든 장면에서 말을 완벽히 잘하는 화자는 드물다. 자신이 말을 잘하고 싶은 영역별 목표(설득&amp;middot;유희&amp;middot;협상 등)를 구체화한 후 개별적으로 연습해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설득의 기술, 재미있게 말하는 법, 협상하는 법 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.2 화술 목표의 세분화&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;설득&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;논리&amp;middot;신뢰&amp;middot;감정 호소&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;제안 발표, 영업 미팅&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;유희&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;흥미&amp;middot;공감&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;친목 모임, 강연 아이스브레이크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;협상&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;이해관계 조율&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;가격 협의, 일정 조정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Part 2 |  말하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;전&lt;/b&gt;&lt;b&gt;: 콘텐츠 채우기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.1 &amp;ldquo;Garbage In, Garbage Out&amp;rdquo; 원칙&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;입력의 질&lt;/b&gt;&lt;/span&gt;이 출력의 질을 결정한다.&lt;/li&gt;
&lt;li&gt;형편없는 글과 말을 읽고 들으면, 말하는 것도 따라간다.&lt;/li&gt;
&lt;li&gt;자주 듣는 말에 따라 내 말의 수준이 결정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.2 양질의 입력을 위한 세 가지 요소&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;독창적 경험&amp;middot;지식&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누구나 다 아는 이야기는 흥미가 가지 않는다.&lt;/li&gt;
&lt;li&gt;다수가 이미 겪은 일만 소재로 삼을 때, 화제의 희소성이 사라진다.&lt;/li&gt;
&lt;li&gt;특히 요즘같은 시대에 SNS의 영향으로 모두가 비슷한 경험을 지향한다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;예:&lt;/i&gt; 흔한 여행 후기는 식상하지만, 현지인 인터뷰&amp;middot;실패담 등은 신선하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인사이트(사고&amp;middot;성찰)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혼자 숙고하거나, 평소에 대화하지 않는 배경의 인물과 대화할 때 깊이가 생긴다.&lt;/li&gt;
&lt;li&gt;유행보다는 독특한 경험을 좇고, 특이한 지식을 습득한 후에는 혼자 시간을 갖고 고찰해야 한다.&lt;/li&gt;
&lt;li&gt;그러나 얻은 인사이트를 기록하지 않으면 잊어버린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기록 시스템&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;(1) 빠른 메모 &amp;rarr; (2) 문서화 &amp;rarr; (3) 스크립트 작성&lt;/b&gt;&lt;span&gt;의 삼단 구조.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;생략하면 인사이트는 망각되거나 단편화된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.3 직장인 대화 목적 네 가지&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정보 전달&lt;/li&gt;
&lt;li&gt;요청(리소스&amp;middot;협조)&lt;/li&gt;
&lt;li&gt;업무 지시&lt;/li&gt;
&lt;li&gt;피드백&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;대화 전 해당 목적을 명확히 규정하면, 군더더기 없는 발화가 가능하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Part 3 |  말하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;전&lt;/b&gt;&lt;b&gt;: 상대 맞춤 준비&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.1 자리의 &amp;lsquo;암묵적 목적&amp;rsquo; 탐색&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;친구가 밥을 살 때 &amp;ldquo;자랑하고 싶은 무언가&amp;rdquo;가 숨어 있을 수 있다.&lt;/li&gt;
&lt;li&gt;목적을 오인하면 동상이몽의 대화가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.2 사전 정보 수집&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대 SNS&amp;middot;저서&amp;middot;인터뷰를 통해 관심사&amp;middot;최근 활동을 파악한다.&lt;/li&gt;
&lt;li&gt;공통분모(읽은 책, 취미 등)를 한두 개 확보하면 첫 서두가 부드럽다.&lt;/li&gt;
&lt;li&gt;프랑스 일부 가정이 &lt;b&gt;&amp;lsquo;입구 책장&amp;rsquo;&lt;/b&gt;을 통해 대화 주제를 마련하는 사례를 참고할 만하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Part 4 |  말하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;중&lt;/b&gt;&lt;b&gt;: 주제 선정과 스킬 운용&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.1 핵심 구조 틀&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;PREP&lt;/b&gt;&lt;/span&gt;(Point&amp;ndash;Reason&amp;ndash;Example&amp;ndash;Point) 혹은 &lt;span&gt;&lt;b&gt;서론&amp;ndash;본론&amp;ndash;결론&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;구조화된 발화는 정보 손실을 방지하고, 청자의 이해를 가속한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.2 호응 신호와 주제 전환&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발화 후 청중의 &lt;i&gt;아이컨택 지속, 미세 추임새(&amp;ldquo;맞아요&amp;rdquo;) 감소&lt;/i&gt;&amp;nbsp;등을 살핀다.&lt;/li&gt;
&lt;li&gt;반응이 미미하면 해당 주제는 과감히 종료하고 새 주제로 이동한다.&lt;/li&gt;
&lt;li&gt;과거 주제를 억지로 되풀이하면 &lt;span&gt;&lt;b&gt;대화 리듬&lt;/b&gt;&lt;/span&gt;이 단절된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.3 &amp;lsquo;논리 갑질&amp;rsquo; 3P 차단&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Preach&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;ldquo;네가 몰라서 그래&amp;rdquo; 식 설교&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;질문으로 이해도 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Prosecute&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;잘못 추궁&amp;middot;비난&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;사실&amp;middot;영향 분리 후 협력 방안 제시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Politicking&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;정치질&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람들의 대화는 &lt;b&gt;논리 갑질&lt;/b&gt;로 물들기 쉽다.&lt;/li&gt;
&lt;li&gt;오히려 대화의 대부분이 논리 갑질인 경우가 많다.&lt;/li&gt;
&lt;li&gt;3P를 제거하고 빈 공간에 경험, 지식, 인사이트를 채우자.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Part 5 |  말하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;후&lt;/b&gt;&lt;b&gt;: 피드백과 관계 관리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.1 좋은 대화의 지표&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대화 전후 &lt;span&gt;&lt;b&gt;사고 변동&lt;/b&gt;&lt;/span&gt;이 일어났는가?&lt;/li&gt;
&lt;li&gt;3P를 자제한 채 상대 의견을 수용했는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.2 사후 피드백 절차&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;자기 점검 노트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공&amp;middot;실수&amp;middot;느낀 점을 간결히 기록.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필요 시 사과&amp;middot;정정 연락&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉시 메시지 또는 이메일로 오해를 해소한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장기적 관계 유지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대화에서 얻은 인사이트를 바탕으로 후속 자료&amp;middot;링크를 공유한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;맺음말&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18세기 외교관 &lt;span&gt;&lt;b&gt;탈레랑&lt;/b&gt;&lt;/span&gt;은 &amp;ldquo;외교의 기본은 타국의 얼굴이 붉어지지 않게 하는 것&amp;rdquo;이라 설파하였다. 이는 곧 현대적 대화에서도 유효한 원칙이다. 또한 &lt;span&gt;&lt;b&gt;데일 카네기&lt;/b&gt;&lt;/span&gt;는 논쟁을 통한 설득의 한계를 지적하며 &amp;ldquo;상대를 이기려 하기보다 이해하라&amp;rdquo;고 강조했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이번 주, 나와 경험이 전혀 다른 지인과 30분 이상 대화하기.&lt;/li&gt;
&lt;li&gt;대화 직후 3P 사용 여부를 점검하고 노트에 기록하기.&lt;/li&gt;
&lt;li&gt;얻은 인사이트 중 하나를 짧은 스크립트 형태로 정리해보기.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;부록&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;A. 체크리스트 (요약)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대화 목적&amp;middot;상대&amp;middot;상황 정의&lt;/li&gt;
&lt;li&gt;고유 경험&amp;middot;인사이트 확보&lt;/li&gt;
&lt;li&gt;빠른 메모 &amp;rarr; 문서화 &amp;rarr; 스크립트 작성&lt;/li&gt;
&lt;li&gt;상대 정보 사전 조사&lt;/li&gt;
&lt;li&gt;PREP 구조 적용&lt;/li&gt;
&lt;li&gt;3P(Preach&amp;middot;Prosecute&amp;middot;Politicking) 자제&lt;/li&gt;
&lt;li&gt;대화 후 피드백 노트 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;B. 스크립트 템플릿(예시)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;1. 서론(Attention)  
   - 후킹 질문 / 흥미로운 사실

2. 본론(Body)  
   2.1 Point 1  
       - 이유&amp;middot;근거  
       - 사례  
   2.2 Point 2 &amp;hellip;

3. 결론(Closure)  
   - 핵심 메시지 재진술  
   - 행동 요청 또는 여운&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;C. 참고 자료&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미키피디아, &lt;a href=&quot;https://youtu.be/mn3NV7vVIzM?si=Wo3vSchypu8qe4Hm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&amp;lt;말 잘하는 사람들의 비결은 뭘까? with 조승연 작가&amp;gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dale Carnegie, &lt;i&gt;How to Win Friends and Influence People&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/622</guid>
      <comments>https://insengnewbie.tistory.com/622#entry622comment</comments>
      <pubDate>Sat, 14 Jun 2025 12:52:12 +0900</pubDate>
    </item>
    <item>
      <title>센디 위스키 모임</title>
      <link>https://insengnewbie.tistory.com/621</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1cvct/btsOq8wd63L/zylA7hrhTf9TLdK3WXEVq1/tfile.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1cvct/btsOq8wd63L/zylA7hrhTf9TLdK3WXEVq1/tfile.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1cvct/btsOq8wd63L/zylA7hrhTf9TLdK3WXEVq1/tfile.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1cvct%2FbtsOq8wd63L%2FzylA7hrhTf9TLdK3WXEVq1%2Ftfile.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;4000&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;즐겁군&lt;/p&gt;</description>
      <category>혼잣말</category>
      <category>센디</category>
      <category>위스키</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/621</guid>
      <comments>https://insengnewbie.tistory.com/621#entry621comment</comments>
      <pubDate>Fri, 6 Jun 2025 01:22:27 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자도 알아야 할 의존성 주입(DI): 리액트에서 바로 써먹는 DI 개념</title>
      <link>https://insengnewbie.tistory.com/620</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vBqjl/btsOoN0aA4W/wxo2YRN3c3XNqKLuSxMCx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vBqjl/btsOoN0aA4W/wxo2YRN3c3XNqKLuSxMCx1/img.png&quot; data-alt=&quot;gpt4o&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vBqjl/btsOoN0aA4W/wxo2YRN3c3XNqKLuSxMCx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvBqjl%2FbtsOoN0aA4W%2Fwxo2YRN3c3XNqKLuSxMCx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;409&quot; height=&quot;409&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;gpt4o&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하다 보면 자연스럽게 상태 관리 도구를 사용하게 됩니다. 특히 리액트 개발자라면 Context API나 Zustand 같은 상태 관리 라이브러리를 익숙하게 쓰게 됩니다. 그런데 이렇게 상태를 외부에서 주입받아 쓰는 방식이, 사실은 &amp;ldquo;의존성 주입(Dependency Injection, DI)&amp;ldquo;이라는 개념이라는 사실을 알고 계셨나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입은 주로 백엔드, 특히 스프링(Spring) 프레임워크에서 자주 언급되는 개념입니다. 그래서 많은 프론트엔드 개발자들이 DI라는 용어를 들으면 &amp;lsquo;그건 백엔드 개발자들이 쓰는 거 아니었어?&amp;rsquo; 하고 생각합니다. 저 역시 처음엔 그랬습니다. 그러나 알고 보니, 제가 리액트 코드에서 자주 쓰던 바로 그 방식이 의존성 주입이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 프론트엔드 개발자들이 놓치기 쉬웠던 DI 개념을 쉽고 정확하게 이해할 수 있도록 돕겠습니다. 단순한 이론 설명이 아니라 React와 TypeScript를 활용한 구체적인 예제를 통해 직접 체험하고 익힐 수 있게 구성했습니다. 의존성 주입의 개념을 제대로 이해하고 활용하면, 앞으로 프론트엔드 개발 능력 향상은 물론이고 백엔드 개발자들과의 협업에서도 더욱 수월하게 소통할 수 있게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DI라는 말을 언제 들었더라?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwviby/btsOpzG0yUa/yZtbA1720Vce7p1bVvE241/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwviby/btsOpzG0yUa/yZtbA1720Vce7p1bVvE241/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwviby/btsOpzG0yUa/yZtbA1720Vce7p1bVvE241/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwviby%2FbtsOpzG0yUa%2FyZtbA1720Vce7p1bVvE241%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;445&quot; height=&quot;310&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 회사에서 일할 때였습니다. 점심시간에 동료 백엔드 개발자들이 식사를 하면서 대화하는 중, &quot;이 부분은 DI로 주입하면 편하잖아?&quot; 같은 이야기를 주고받는 것을 들었습니다. 당시 저는 그들의 말을 이해하지 못하고 고개만 끄덕이며 '백엔드 개발자들끼리만 이해하는 어려운 용어구나'라고 생각했습니다. 마치 외국어를 듣는 것처럼 낯설고 어렵게 느껴졌던 기억이 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 리액트에서 Context API로 상태 관리를 하며 코드를 짜다가 문득 아티클에서 '이 방식이 바로 DI다'라는 설명을 보고 깜짝 놀랐습니다. 제가 매일 편하게 쓰던 방식이 바로 그 어렵게만 느껴졌던 DI였다는 사실을 깨닫는 순간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프론트엔드에서 의존성 주입이란 개념이 생소한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션이 초기에는 화면을 그리는 것이 주목적이었기 때문에, 화면 로직 자체에서 필요한 API 호출이나 상태 관리를 컴포넌트 내부에서 바로 처리하는 경우가 많았습니다. 예를 들어, 버튼 클릭 시 바로 fetch를 호출하거나 setState를 바로 사용하면서 컴포넌트가 스스로 모든 의존을 해결하도록 작성해 왔죠. 이러한 패턴이 오랫동안 관습처럼 굳어지면서, 별도로 &amp;ldquo;외부에서 의존을 주입한다&amp;rdquo;는 생각 자체가 자연스럽지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, React나 Vue 같은 프레임워크가 등장했을 때는 컴포넌트 단위로 상태와 로직을 함께 묶어서 관리하는 방식이 중심이었고, 서비스 계층이나 비즈니스 로직을 분리하기보다는 컴포넌트 내부에서 한 번에 처리하는 게 효율적이라고 여겨졌습니다. 그래서 백엔드에서 흔히 쓰이는 DI 컨테이너나 IoC(Inversion of Control) 프레임워크 같은 개념이 프론트엔드 쪽에는 잘 전파되지 않았습니다. 이 때문에 &amp;ldquo;의존성 주입&amp;rdquo;이라는 키워드를 찾아봐도 프론트엔드 관련 글이나 예제가 부족했고, 실제로 실무 프로젝트에서도 DI 패턴을 적용한 사례가 많지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념 정리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;의존성 역전 원칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입에 대해 알기 위해서는 먼저 &quot;의존성 역전 원칙(DIP, Dependency Inversion Principle)&quot;에 대해 알아야 합니다. 의존성 주입은 DIP를 실현하기 위해 주로 사용하는 패턴이기 때문입니다. 다만 아래처럼 고수준 모듈이 어떻고.. 하면 이해하기 어려울 수도 있으니 리액트 기준으로 설명드리겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;고수준 컴포넌트(화면을 구성하는 컴포넌트)&lt;/b&gt;는&lt;/li&gt;
&lt;li&gt;&amp;ldquo;추상화된 서비스 인터페이스(API 함수 인터페이스, 로깅 함수 인터페이스 등)&amp;rdquo;에만 의존하며,&lt;/li&gt;
&lt;li&gt;&amp;ldquo;구체적인 서비스 구현(직접 fetch하는 코드, 직접 console.log하는 코드, Mock 구현체 등)&amp;rdquo;은 따로 분리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트 입장에서는 &amp;lsquo;fetchUser(id): Promise&amp;rsquo; 같은 함수 시그니처만 알면 되고, 실제 네트워크 통신 코드는 하위 모듈에서 구현합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 역전 원칙(Dependency Inversion Principle)이란 SOLID 원칙 중 하나로, 고수준 모듈이 저수준 모듈에 의존하지 않고 둘 다 추상화에 의존해야 한다는 설계 원칙입니다. 고수준 모듈이 저수준 모듈의 구체 구현에 묶이지 않고, 둘 다 &amp;lsquo;추상화 계층(인터페이스)&amp;rsquo;에 의존하도록 설계함으로써 결합도를 낮추는 원칙. 간단히 말해, '어떻게'가 아니라 '무엇'을 해야 할지만 하위 모듈이 알고, 구체적인 실행 방법은 외부에서 결정되는 구조입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;의존성 주입(DI)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 의존성 주입에 대해 알아보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입이란 컴포넌트가 스스로 자신의 의존(dependency)을 생성하거나 가져오지 않고, 외부에서 필요한 의존을 주입(inject) 받는 패턴입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 나쁜 예: 구체적인 구현에 의존
function OrderService() {
  const createOrder = (orderData) =&amp;gt; {
    // 주문 처리 로직
    console.log('주문이 생성되었습니다:', orderData.id); // console에 직접 의존

    if (orderData.amount &amp;gt; 100000) {
      console.error('고액 주문 알림'); // console에 직접 의존
    }
  };

  return { createOrder };
}

// 좋은 예: 추상화된 로거에 의존
function OrderService(logger) {
  const createOrder = (orderData) =&amp;gt; {
    // 주문 처리 로직
    logger.info('주문이 생성되었습니다:', orderData.id);

    if (orderData.amount &amp;gt; 100000) {
      logger.warn('고액 주문 알림');
    }
  };

  return { createOrder };
}

// 다양한 로거 구현체
const consoleLogger = {
  info: (message) =&amp;gt; console.log(`[INFO] ${message}`),
  warn: (message) =&amp;gt; console.warn(`[WARN] ${message}`),
  error: (message) =&amp;gt; console.error(`[ERROR] ${message}`)
};

const fileLogger = {
  info: (message) =&amp;gt; writeToFile(`[INFO] ${message}`),
  warn: (message) =&amp;gt; writeToFile(`[WARN] ${message}`),
  error: (message) =&amp;gt; writeToFile(`[ERROR] ${message}`)
};

// 사용
const orderService = OrderService(consoleLogger); // 또는 fileLogger&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DI 외의 의존성 역전의 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI(의존성 주입)&lt;/b&gt;는 DIP를 구현하는 가장 흔한 방법이지만, 서비스 로케이터, 팩토리 패턴, 플러그인 아키텍처, 콜백/전략 패턴, 이벤트 기반 구조 등 DIP를 만족시키는 여러 패턴이 존재합니다. 이번 글에서는 자세히 다루지 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 역전 원칙을 지켜야 하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 역전 원칙을 따를 때 얻을 수 있는 주된 장점은 &lt;b&gt;결합도(coupling)&lt;/b&gt;가 크게 낮아진다는 점입니다. 화면용 컴포넌트 코드를 작성할 때 &amp;ldquo;어떤 로깅 라이브러리를 쓸지&amp;rdquo;, &amp;ldquo;어떤 API 엔드포인트를 호출할지&amp;rdquo; 같은 구체적인 세부 구현에 얽매이지 않기 때문에, UI 쪽 코드는 언제나 일정한 인터페이스만 바라보고 동작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, DIP를 지키면 코드 유지보수성이 향상됩니다. 프로젝트 초기에는 작은 기능만 구현해도 충분하지만, 규모가 커질수록 서로 다른 팀이나 개발자가 동시에 여러 부분을 고쳐야 하는 상황이 자주 발생합니다. 이때 인터페이스만 공유되어 있으면, 누군가는 뒤에서 API 호출 로직을 수정하고, 다른 누군가는 화면 컴포넌트 로직을 개발해도 서로 충돌 없이 작업할 수 있습니다. 반대로 DIP를 지키지 않고 컴포넌트가 직접 fetch()를 호출하거나 console.log()를 쓰도록 결합되어 있다면, 특정 모듈을 변경할 때 불필요하게 연쇄적인 수정을 해야 해서 버그가 발생할 위험도 커지고, 협업 생산성도 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, DIP를 준수하는 것은 프론트엔드 코드의 유연성, 확장성, 테스트 용이성을 크게 높여주며, 프로젝트가 성장하거나 요구사항이 바뀔 때 최소한의 수정만으로 대응할 수 있게 해줍니다. 따라서 규모가 조금이라도 커지는 애플리케이션이라면 &amp;lsquo;언제든지 내부 구현을 교체해도 상위 로직은 그대로 동작한다&amp;rsquo;는 설계를 염두에 두는 것이 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그렇다면 React에서는 무엇이 의존성 주입인가?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9f2UL/btsOp9HqCWZ/mS1FK8KRKGOhKLKkhg9Kf0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9f2UL/btsOp9HqCWZ/mS1FK8KRKGOhKLKkhg9Kf0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9f2UL/btsOp9HqCWZ/mS1FK8KRKGOhKLKkhg9Kf0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/9f2UL/btsOp9HqCWZ/mS1FK8KRKGOhKLKkhg9Kf0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;362&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 대표적으로 다음과 같은 세 가지 방식으로 DI를 구현할 수 있습니다. 코드 예제를 통해 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Props를 통해 주입하기&lt;/li&gt;
&lt;li&gt;커스텀 훅을 통해 주입하기&lt;/li&gt;
&lt;li&gt;Context API 또는 Zustand 같은 전역 상태 관리 툴을 통해 주입하기&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Props를 통해 주입하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적이고 직접적인 방식은 Props를 이용하는 것입니다. 컴포넌트가 필요로 하는 의존성을 부모 컴포넌트로부터 전달받습니다. props는 간편하지만, 단계가 깊어질수록 props drilling 문제가 생길 수 있으므로 커스텀 훅 또는 Context API를 주로 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// API 호출을 추상화한 서비스
function UserService(fetchUser) {
  return {
    getUser: (id) =&amp;gt; fetchUser(id)
  };
}

// 구체적인 API 호출 함수
const fetchUserAPI = (id) =&amp;gt; fetch(`/api/users/${id}`).then(res =&amp;gt; res.json());

// 부모 컴포넌트에서 UserService를 생성하여 props로 전달
function UserProfileContainer() {
  const userService = UserService(fetchUserAPI);
  return &amp;lt;UserProfile userService={userService} /&amp;gt;;
}

// 자식 컴포넌트는 주입받은 userService만 사용
function UserProfile({ userService }) {
  const [user, setUser] = useState(null);

  useEffect(() =&amp;gt; {
    userService.getUser(1).then(setUser);
  }, [userService]);

  return user ? &amp;lt;div&amp;gt;{user.name}&amp;lt;/div&amp;gt; : &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서는 의존성 주입의 핵심 원칙들을 보여주고 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;추상화된 인터페이스&lt;/b&gt;: &lt;code&gt;UserService&lt;/code&gt;는 구체적인 API 호출 방식(&lt;code&gt;fetch&lt;/code&gt;)에 의존하지 않고, 추상화된 &lt;code&gt;fetchUser&lt;/code&gt; 함수를 주입받습니다. 이는 의존성 역전 원칙(DIP)을 따르는 좋은 예입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성 분리&lt;/b&gt;: &lt;code&gt;UserProfile&lt;/code&gt; 컴포넌트는 API 호출 로직을 직접 구현하지 않고, 주입받은 &lt;code&gt;userService&lt;/code&gt;를 통해 사용자 데이터를 가져옵니다. 이렇게 하면 컴포넌트는 UI 렌더링에만 집중할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 용이성&lt;/b&gt;: 실제 API 대신 Mock 구현체를 주입할 수 있어 단위 테스트가 쉬워집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커스텀 훅을 통해 주입하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 훅(Custom Hooks)&lt;/b&gt;은 React에서 의존성 주입을 구현하는 방법 중 하나입니다. 컴포넌트가 직접 의존성을 생성하거나 가져오는 대신, 커스텀 훅을 통해 필요한 서비스나 로직을 주입받을 수 있습니다. 이는 컴포넌트의 관심사를 분리하고, 테스트와 재사용성을 높이는 데 도움이 됩니다. 특히 비즈니스 로직이나 외부 서비스와의 통신을 추상화하여 컴포넌트가 순수하게 UI 렌더링에만 집중할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// userService 훅
const useUserService = () =&amp;gt; {
  const fetchUser = useCallback(async (id) =&amp;gt; {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  }, []);

  return { fetchUser };
};

// 컴포넌트 내부에서 커스텀 훅 사용
function UserProfile() {
  const { fetchUser } = useUserService();
  const [user, setUser] = useState(null);

  useEffect(() =&amp;gt; {
    fetchUser(1).then(setUser);
  }, [fetchUser]);

  return user ? &amp;lt;div&amp;gt;{user.name}&amp;lt;/div&amp;gt; : &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Context API로 주입하기&lt;/h4&gt;
&lt;pre id=&quot;code_1749058445840&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Context 생성
const UserContext = createContext();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 의존성(DI의 dependency)을 나타내는 &lt;span&gt;&lt;b&gt;추상적인 컨텍스트(UserContext)&lt;/b&gt;&lt;/span&gt; 를 정의합니다. 여기서 &lt;span&gt;&lt;b&gt;UserContext&lt;/b&gt;&lt;/span&gt;는 컴포넌트가 실제로 필요한 유저 정보나 메서드 등 구체적인 데이터를 제공하지 않고, 단지 &amp;ldquo;어떤 정보나 메서드가 있을 것&amp;rdquo;이라는 추상적인 인터페이스 역할만 합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// Provider 컴포넌트 생성
export const UserProvider = ({ children }) =&amp;gt; {
  const [user, setUser] = useState(null);

  const fetchUser = useCallback(async (id) =&amp;gt; {
    const response = await fetch(`/api/users/${id}`);
    const userData = await response.json();
    setUser(userData);
  }, []);

  const updateUser = useCallback((userData) =&amp;gt; {
    // 업데이트 로직을 여기에 추가할 수 있음
    setUser(userData);
  }, []);

  const logout = useCallback(() =&amp;gt; {
    setUser(null);
  }, []);

  // 제공할 값 설정
  const value = {
    user,
    fetchUser,
    updateUser,
    logout,
  };

  return (
    &amp;lt;UserContext.Provider value={value}&amp;gt;
      {children}
    &amp;lt;/UserContext.Provider&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 실제 의존성을 구현하고 제공합니다. &lt;span&gt;UserProvider&lt;/span&gt;는 유저 상태(&lt;span&gt;user&lt;/span&gt;)와 이를 관리할 수 있는 메서드(&lt;span&gt;fetchUser&lt;/span&gt;, &lt;span&gt;updateUser&lt;/span&gt;, &lt;span&gt;logout&lt;/span&gt;)를 구체적으로 구현하여 자식 컴포넌트들에게 제공(&lt;span&gt;&lt;b&gt;inject&lt;/b&gt;&lt;/span&gt;)합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 단계에서 &amp;ldquo;무엇을 주입할지&amp;rdquo; 결정됩니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const UserProfile = ({ userId }) =&amp;gt; {
  const { user, fetchUser, logout } = useContext(UserContext);

  useEffect(() =&amp;gt; {
    fetchUser(userId);
  }, [fetchUser, userId]);

  if (!user) return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{user.name}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{user.email}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={logout}&amp;gt;Logout&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default UserProfile;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트는 &lt;span&gt;&lt;b&gt;useContext&lt;/b&gt;&lt;/span&gt;를 통해 앞서 정의된 추상적 의존성(&lt;span&gt;&lt;b&gt;UserContext&lt;/b&gt;&lt;/span&gt;)을 주입받습니다. 직접적으로 &lt;span&gt;fetch&lt;/span&gt; API 호출이나 상태 관리를 하지 않고, 주입받은 메서드와 상태(&lt;span&gt;fetchUser&lt;/span&gt;, &lt;span&gt;logout&lt;/span&gt;, &lt;span&gt;user&lt;/span&gt;)를 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 &lt;span&gt;&lt;b&gt;컴포넌트는 내부 구현에 의존하지 않고&lt;/b&gt;&lt;/span&gt;, 단지 제공된 인터페이스(의존성)에만 의존하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgvUSZ/btsOqbFh4rR/JURIFn0QNgetE26EDRNSG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgvUSZ/btsOqbFh4rR/JURIFn0QNgetE26EDRNSG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgvUSZ/btsOqbFh4rR/JURIFn0QNgetE26EDRNSG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgvUSZ%2FbtsOqbFh4rR%2FJURIFn0QNgetE26EDRNSG1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 예제를 보셨다면 평소에 했던 개발과 크게 다르지 않음을 알 수 있습니다. 많은 프론트엔드 개발자들이 의존성 주입(DI)을 일상적으로 사용하고 있음에도 불구하고, 저처럼 그 개념을 인식하지 못하는 경우가 많습니다. 실제로 DI를 모르고도 개발하는데는 문제가 없지만, 이 개념을 제대로 이해하고 의도적으로 활용하는 것은 코드의 품질과 유지보수성에 큰 차이를 만들어낸다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글을 작성하며 저 또한 의존성이란 무엇인지 깊게 공부해볼 수 있었습니다. 프로젝트 규모가 커지며 한 컴포넌트에서 코드 간의 결합도가 높아지고 점점 유지보수하기 어려워짐을 느꼈습니다. 다음번에는 실제 코드 개선 사례를 들고오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@woohm402/dependency-injection-in-reactjs&quot;&gt;https://velog.io/@woohm402/dependency-injection-in-reactjs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.wonderwall.kr/articles/functionaldependencymanagement/&quot;&gt;https://tech.wonderwall.kr/articles/functionaldependencymanagement/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://de-velop-humus.tistory.com/22&quot;&gt;https://de-velop-humus.tistory.com/22&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/620</guid>
      <comments>https://insengnewbie.tistory.com/620#entry620comment</comments>
      <pubDate>Thu, 5 Jun 2025 07:30:01 +0900</pubDate>
    </item>
    <item>
      <title>버전 꼬임 이제 그만: Cursor AI로 &amp;lsquo;update:minor&amp;rsquo; 한 줄에 끝낸 릴리즈 혁명</title>
      <link>https://insengnewbie.tistory.com/619</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5p41D/btsNVk97C5W/pN4nHFo5rnyUJFp8EmLep0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5p41D/btsNVk97C5W/pN4nHFo5rnyUJFp8EmLep0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5p41D/btsNVk97C5W/pN4nHFo5rnyUJFp8EmLep0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5p41D%2FbtsNVk97C5W%2FpN4nHFo5rnyUJFp8EmLep0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;버전 올리기가 이렇게 귀찮다니..&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;안녕하세요. 저는 &lt;span style=&quot;color: #2e58ec;&quot;&gt;&lt;b&gt;화물운송 플랫폼 Sendy&lt;/b&gt;&lt;/span&gt;에서&amp;nbsp;&lt;span&gt;&lt;b&gt;Next.js&lt;/b&gt;&lt;/span&gt;와 &lt;span&gt;&lt;b&gt;TypeScript&lt;/b&gt;&lt;/span&gt;를 주력으로 사용하는 프론트엔드 개발자입니다. 제가 회사에 입사한 지 1년 남짓 되었는데요. 제가 오기 전, 처음 두 명이던 팀에서는 코드리뷰가 강제가 아니어서 &lt;span&gt;package.json&lt;/span&gt; 숫자를 직접 바꾸고 PR을 바로 머지해도 큰 문제가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그러나 세 번째 인원이 합류하면서 코드 리뷰 문화가 정착됐고 소규모 조직으로서의 모습을 갖추었습니다. 개발 인원의 증가에 따라 업무량이 많아지면서 PR이 쌓이기 시작했습니다. 그에 따라 &lt;span&gt;&lt;b&gt;코드 리뷰가 지연&lt;/b&gt;&lt;/span&gt;되기 시작했고, 릴리즈가 제때 이루어지지 않았습니다. 그 결과 &lt;b&gt;&amp;ldquo;이번엔 누가 버전을 올려야 할까요?&amp;rdquo;&lt;/b&gt;라는 질문이 자연스럽게 돌아왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF7hME/btsNU5yzNMM/5cpdIo89ofp9r5JLxc48V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF7hME/btsNU5yzNMM/5cpdIo89ofp9r5JLxc48V1/img.png&quot; data-alt=&quot;사고 당시 슬랙&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF7hME/btsNU5yzNMM/5cpdIo89ofp9r5JLxc48V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF7hME%2FbtsNU5yzNMM%2F5cpdIo89ofp9r5JLxc48V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;32&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사고 당시 슬랙&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;한 번은 제가 &lt;span&gt;버전 업데이트를 깜빡한 채 릴리즈&lt;/span&gt;를 진행했습니다. 배포 후 몇 분 만에 &amp;ldquo;버전은 올리셨나요?&amp;rdquo;라는 슬랙 알림을 받고, 부랴부랴 &lt;b&gt;&amp;lsquo;버전만 올리는 긴급 PR&amp;rsquo;&lt;/b&gt;을 작성해 다시 릴리즈해야 했습니다. 이후로 파트원들도 같은 실수를 반복했습니다. 작은 실수였지만 팀 전체의 흐름이 꼬였고, &amp;ldquo;다음부터는 꼭 자동화해야겠다&amp;rdquo;는 절실함이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;제가 겪었던 세 가지 Pain Point&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;릴리즈 직전의 수동 버전 수정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;package.json&lt;span&gt;과 &lt;/span&gt;package-lock.json&lt;span&gt;을 모두 열어 숫자를 맞춰 적어야 했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;정말 쉬운 일이지만 이슈가 끝날 때마다 같은 일을 반복해야 해서 번거롭고 피곤하게 느껴졌습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제대로 동작하지 않는 npm version&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사내 규칙상 버전을 &lt;span&gt;major.minor&lt;/span&gt; 두 단계로만 유지해 &lt;span&gt;npm version patch&lt;/span&gt; 명령이 맞지 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사소하지만 빈번한 실수&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누군가는 버전을 올렸는데, 다른 사람이 또 올려 &lt;span&gt;&lt;b&gt;충돌이 발생했습니다&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;때로는 버전 올리기를 잊고 PR을 머지해 &lt;span&gt;&lt;b&gt;릴리즈 버전이 꼬였습니다&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;초기 대안 - Semantic Release&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;학습 곡선&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;커밋 메시지 규칙만 익히면 CI가 자동으로 릴리즈했습니다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;자유로운 커밋 문화를 지키고 싶었습니다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;커뮤니티&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;⭐️ 21 만, 플러그인 생태계 풍부&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;플러그인을 이해하고 구성해야 하는 부담&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;CI 통합&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;GitHub Actions 예제가 잘 준비돼 있었습니다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;우리 팀 규모에는 &amp;ldquo;과하다&amp;rdquo;는 의견이 많았습니다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/span&gt;: &amp;ldquo;커밋 규칙 강제&amp;rdquo;가 주는 러닝 커브가 이득보다 크다고 판단했습니다. 그래서 직접 스크립트 코드를 작성하기로 결정했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결책 - &amp;ldquo;라벨 하나면 끝!&amp;rdquo; 전략&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 버전 업데이트 스크립트 코드 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Cursor AI&lt;/b&gt;&lt;/span&gt;에게 &amp;ldquo;&lt;span&gt;major.minor&lt;/span&gt; 체계를 유지하면서 CLI로 버전을 올리는 스크립트를 작성해 달라&amp;rdquo;고 요청했습니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;받은 코드를 &lt;/span&gt;scripts/update-version.js&lt;span&gt;로 저장하고 &lt;/span&gt;node scripts/update-version.js [major | minor]&lt;span&gt; 형태로 호출하도록 했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;package.json&lt;/span&gt;에 다음과 같이 등록해 사용했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;version:major&quot;: &quot;node scripts/update-version.js major&quot;,
  &quot;version:minor&quot;: &quot;node scripts/update-version.js minor&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. GitHub Actions와 라벨링 자동화&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9LW4c/btsNU5L6Nni/YoFWg80btCCpy3Y5a2XOlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9LW4c/btsNU5L6Nni/YoFWg80btCCpy3Y5a2XOlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9LW4c/btsNU5L6Nni/YoFWg80btCCpy3Y5a2XOlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9LW4c%2FbtsNU5L6Nni%2FYoFWg80btCCpy3Y5a2XOlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;337&quot; height=&quot;150&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;라벨 정의&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;update:major&lt;span&gt;, &lt;/span&gt;update:minor&lt;span&gt; 두 가지만 만들었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;머지 트리거&lt;/b&gt;&lt;/span&gt;: PR이 &lt;span&gt;main&lt;/span&gt;에 병합될 때 라벨을 확인해 대응 스크립트를 실행했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CI 스텝 예시&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;on:
  pull_request:
    types: [closed]

jobs:
  bump-version:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true &amp;amp;&amp;amp; github.event.pull_request.base.ref == 'main' &amp;amp;&amp;amp; (contains(github.event.pull_request.labels.*.name, 'update:major') || contains(github.event.pull_request.labels.*.name, 'update:minor')) }}
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node
        uses: actions/setup-node@v4
        with: { node-version: 20 }
      - name: Run version script
        run: |
          LABELS=&quot;${{ join(github.event.pull_request.labels.*.name, ',') }}&quot;
          if [[ &quot;$LABELS&quot; == *&quot;update:major&quot;* ]]; then
            npm run version:major
          elif [[ &quot;$LABELS&quot; == *&quot;update:minor&quot;* ]]; then
            npm run version:minor
          else
            echo &quot;No version label, skipping.&quot;
          fi
      - name: Push tag &amp;amp; version bump
        run: |
          git push --follow-tags&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;CI 실행 시간&lt;/b&gt;&lt;/span&gt;은 약 10&amp;ndash;15 초였으며, 태그 푸시까지 자동으로 처리했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 프로젝트 Github 권한 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;버전을 올린 뒤에는 태그와 함께 &lt;b&gt;&lt;span&gt;git push&lt;/span&gt;&lt;/b&gt;가 이뤄져야 했습니다. 그러나 GitHub Actions가 리포지토리에 &lt;span&gt;&lt;b&gt;쓰기(write) 권한&lt;/b&gt;&lt;/span&gt;을 가지려면, 프로젝트 &lt;span&gt;&lt;b&gt;Admin&lt;/b&gt;&lt;/span&gt;이 권한을 부여해 주어야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;저는 해결해야 할 문제 상황과 요구 사항을 정리해 회사 &lt;span&gt;&lt;b&gt;CTO님&lt;/b&gt;&lt;/span&gt;께 요청드렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;&amp;ldquo;릴리즈 자동화를 위해 GitHub Actions 봇에게 &lt;span&gt;write&lt;/span&gt; 권한이 필요합니다. 태그-푸시까지 자동으로 이루어지면 버전 꼬임 사고를 많이 없앨 수 있습니다.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CTO님께서는 내용을 검토하시고 &amp;ldquo;정말 좋은 아이디어&amp;rdquo;라며 바로 권한을 열어 주셨습니다. 덕분에 &lt;span&gt;&lt;b&gt;버전 스크립트 &amp;rarr; 태그 푸시&lt;/b&gt;&lt;/span&gt;까지 전 과정이 완전히 자동화됐고, 팀원들은 릴리즈 과정에서 한 단계 더 손을 뗄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과 - 팀원들의 DX가 눈에 띄게 향상됐습니다&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccx1kk/btsNVBwZ4py/yPfNIcS1LpkMAEAzkIvlw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccx1kk/btsNVBwZ4py/yPfNIcS1LpkMAEAzkIvlw1/img.png&quot; data-alt=&quot;Github Actions 18초 소요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccx1kk/btsNVBwZ4py/yPfNIcS1LpkMAEAzkIvlw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccx1kk%2FbtsNVBwZ4py%2FyPfNIcS1LpkMAEAzkIvlw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;113&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Actions 18초 소요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화를 도입한 이후 다음과 같은 효과를 얻었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;ldquo;버전을 올릴 사람&amp;rdquo;&lt;/b&gt;을 정할 필요가 없어 &lt;span&gt;&lt;b&gt;컨텍스트 스위치가 줄었습니다&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;릴리즈 전에 CI가 버전을 올려 주어 &lt;span&gt;&lt;b&gt;버전이 꼬이는 일이 사라졌습니다&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;단점으로는 CI가 종료될 때까지 기다려야 한다는 점이 있지만, 대개 1분 내외여서 큰 불편은 없었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;무엇보다 스스로 느끼는 가장 큰 장점은 &lt;i&gt;&quot;같은 일을 반복하지 않아도 된다!&quot;&lt;/i&gt;는 사실이었습니다. &quot;지금 이대로도 문제 없는데 왜 굳이 개선해?&quot; 라며 현 상황을 유지했다면 지금까지도 자주 버전 업데이트를 잊고 긴급 PR을 작성했을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 과제 - 더 똑똑한 자동화?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;라벨 자동 추천&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: PR 제목에 &lt;/span&gt;feat:&lt;span&gt;가 있으면 &lt;/span&gt;update:minor&lt;span&gt;, &lt;/span&gt;BREAKING CHANGE&lt;span&gt;가 있으면 &lt;/span&gt;update:major&lt;span&gt;를 자동으로 제안할 계획입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;챗봇 UI 도입&lt;/b&gt;&lt;/span&gt;: Slack 봇이 &amp;ldquo;다음 버전을 vX.Y로 올릴까요?&amp;rdquo;라고 묻고, 팀원이 클릭 한 번으로 확정하는 방식을 구상하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자동화를 도입하기 전에는 릴리즈 때마다 &amp;ldquo;혹시 또 버전을 놓치지 않을까&amp;rdquo; 하는 불안이 머릿속을 떠나지 않았습니다. 푸시한 뒤에도 한 번 더 파일을 열어 숫자가 맞는지 다시 확인했고, 배포하기 직전에는 작은 오타 하나에도 손이 떨렸습니다. 하지만 라벨 + Cursor AI 조합을 적용한 뒤로는 &lt;b&gt;&amp;ldquo;버전 걱정&amp;rdquo;&lt;/b&gt;이 업무 목록에서 완전히 사라졌습니다. 덕분에 리뷰 코멘트를 좀 더 세심하게 남길 수 있었고, 회의 시간에도 기능 기획에 집중할 여유가 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RI5LA/btsNVArkbfE/cIvblxGbpt1bfO9rB3LrhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RI5LA/btsNVArkbfE/cIvblxGbpt1bfO9rB3LrhK/img.png&quot; data-alt=&quot;자동화 도입 직후 슬랙 채널&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RI5LA/btsNVArkbfE/cIvblxGbpt1bfO9rB3LrhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRI5LA%2FbtsNVArkbfE%2FcIvblxGbpt1bfO9rB3LrhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;131&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자동화 도입 직후 슬랙 채널&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;팀 분위기도 눈에 띄게 달라졌습니다. 파트리드께서는 &amp;ldquo;릴리즈 체크리스트에서 버전 항목을 지웠다&amp;rdquo;며 만족해하셨고, 파트원 역시 &quot;배포 절차가 단순해져서 좋다&quot;는 소감을 남겼습니다. 작은 자동화지만, &amp;ldquo;우리가 실수할 수도 있는 영역&amp;rdquo;을 시스템이 대신 맡아 준다는 사실만으로도 팀원이 느끼는 &lt;span&gt;&lt;b&gt;심리적 안전감&lt;/b&gt;&lt;/span&gt;이 크게 높아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;버전 관리는 겉보기에 사소해 보여도, 매주 반복된다면 팀의 생산성을 좌우하는 핵심 루틴입니다. 하루 투자로 완성한 이 간단한 자동화가 저희 팀의 개발 경험을 한층 쾌적하게 만들었습니다. 여러분도 팀 상황에 맞는 가벼운 자동화를 도입해 보시길 권해드립니다. &lt;span&gt;&lt;b&gt;작은 귀찮음을 없애는 순간, 더 재미있는 문제에 집중할 힘이 생깁니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참조&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/semantic-release/semantic-release&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/semantic-release/semantic-release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <category>semantic-release</category>
      <category>자동화</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/619</guid>
      <comments>https://insengnewbie.tistory.com/619#entry619comment</comments>
      <pubDate>Tue, 13 May 2025 02:27:29 +0900</pubDate>
    </item>
    <item>
      <title>[FE] 브라우저 동작방식</title>
      <link>https://insengnewbie.tistory.com/618</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발자로서 브라우저가 내부적으로 어떻게 동작하는지 이해하고 싶었고, ChatGPT와 Gemini를 사용한 교차 검증을 통해 이해도를 높일 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저 주 구성요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wopra/btsNLtNhRA0/NrBFf9j4GgSj05kAL2QG1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wopra/btsNLtNhRA0/NrBFf9j4GgSj05kAL2QG1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wopra/btsNLtNhRA0/NrBFf9j4GgSj05kAL2QG1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWopra%2FbtsNLtNhRA0%2FNrBFf9j4GgSj05kAL2QG1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;500&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 메인&amp;nbsp;스레드(Main&amp;nbsp;Thread)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript 실행과 DOM 렌더링을 담당하는 단일 스레드&lt;/li&gt;
&lt;li&gt;호출 스택(call stack)에 쌓인 작업을 순차적으로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2. Heap&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체, 배열, 함수 같은 동적 할당 메모리를 저장하는 공간&lt;/li&gt;
&lt;li&gt;가비지 컬렉터가 주기적으로 사용하지 않는 메모리를 해제&lt;/li&gt;
&lt;li&gt;React의 Virtual DOM은 자바스크립트 객체 방식으로 구현되기 때문에 결국 JS 엔진의 힙(Heap) 메모리 위에 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3. 내부&amp;nbsp;스레드&amp;nbsp;풀(Internal&amp;nbsp;Thread&amp;nbsp;Pool)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 요청(fetch), 파일 I/O, 렌더링, 타이머(setTimeout 등)를 처리&lt;/li&gt;
&lt;li&gt;메인 스레드를 블록하지 않고 백그라운드에서 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;4. 이벤트&amp;nbsp;루프(Event&amp;nbsp;Loop)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드 내에서 &amp;ldquo;스택 비어 있을 때&amp;rdquo; 큐에 있는 콜백을 가져와 실행&lt;/li&gt;
&lt;li&gt;비동기 API의 콜백들을 스케줄링하는 중앙 스케줄러&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;5. 태스크&amp;nbsp;큐(Task&amp;nbsp;Queue,&amp;nbsp;매크로태스크&amp;nbsp;큐)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setTimeout, setInterval, I/O 콜백, UI 이벤트 등&lt;/li&gt;
&lt;li&gt;이벤트 루프가 마이크로태스크를 모두 처리한 뒤 매크로태스크를 하나씩 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;6. 마이크로태스크&amp;nbsp;큐(Microtask&amp;nbsp;Queue)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise.then/.catch, queueMicrotask, MutationObserver 콜백&lt;/li&gt;
&lt;li&gt;매크로태스크보다 우선적으로 모두 비운 후에야 다음 매크로태스크로 넘어감&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내부 스레드 풀&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네트워크 요청, 파일 I/O, 타이머 등과 같은 Web API 관련 작업을 처리하는 메커니즘은 단일 스레드가 아니라 병렬 처리가 가능한 멀티 스레드 방식&lt;/b&gt;으로 동작합니다. 이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;5:1-7:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;5:1-5:158&quot;&gt;&lt;b&gt;메인 스레드 블로킹 방지:&lt;/b&gt; 만약 이러한 작업들이 메인 스레드(JavaScript 실행 및 렌더링 담당)에서 동기적으로 처리된다면, 네트워크 응답을 기다리거나 파일을 읽는 동안 브라우저 전체가 멈추는 현상(blocking)이 발생합니다. 이는 사용자 경험에 치명적입니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;6:1-7:0&quot;&gt;&lt;b&gt;동시성 및 효율성:&lt;/b&gt; 여러 네트워크 요청을 동시에 보내거나, 타이머를 설정하고 다른 작업을 동시에 처리하는 등 병렬 처리를 통해 브라우저는 훨씬 효율적으로 동작할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 방식&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;10:1-14:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;10:1-10:118&quot;&gt;메인 스레드에서 Workspace()나 setTimeout() 같은 비동기 API를 호출하면, 브라우저는 이 작업을 직접 수행하는 대신 해당 요청을 내부의 &lt;b&gt;백그라운드 스레드들&lt;/b&gt;에게 위임합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;11:1-11:138&quot;&gt;이 백그라운드 스레드들은 네트워크 통신, 파일 시스템 접근, 타이머 대기 등의 실제 작업을 수행합니다. 이 작업들은 메인 스레드와는 별개로 동시에 여러 개가 진행될 수 있습니다. (정확히는 스레드 풀 내의 스레드들이 작업을 나누어 처리합니다.)&lt;/li&gt;
&lt;li data-sourcepos=&quot;12:1-12:135&quot;&gt;백그라운드 스레드에서 작업이 완료되면 (예: 네트워크 응답 도착, 타이머 시간 만료), 해당 작업과 연결된 콜백 함수를 &lt;b&gt;태스크 큐(Task Queue)&lt;/b&gt; 또는 &lt;b&gt;마이크로태스크 큐(Microtask Queue)&lt;/b&gt;에 넣습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;13:1-14:0&quot;&gt;메인 스레드의 &lt;b&gt;이벤트 루프&lt;/b&gt;는 호출 스택(Call Stack)이 비었을 때, 큐에서 콜백 함수를 가져와 &lt;b&gt;메인 스레드에서 실행&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async/await와 Promise&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Promise 객체&lt;/b&gt;&lt;/span&gt;는 일반 자바스크립트 객체이기 때문에 어떤 스레드나 큐로 &amp;ldquo;이동&amp;rdquo;하지 않습니다. &lt;span&gt;하지만, &lt;/span&gt;&lt;b&gt;Promise에 연결된 .then/.catch 등의 후속(continuation) 콜백(callback)&lt;/b&gt;&lt;span&gt; 은 항상 &lt;/span&gt;&lt;b&gt;마이크로태스크 큐(microtask queue)&lt;/b&gt;&lt;span&gt; 에 스케줄되므로, 현재 콜 스택이 완전히 비워진 뒤에 실행됩니다. Promise 내부에서 resolve 호출로 상태가 fulfilled 상태가 되지만, .then 핸들러는 즉시 호출되지 않고 마이크로태스크 큐에 등록됩니다. 현재 스택과 동기 코드가 모두 마친 뒤 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.then()&lt;span&gt;, &lt;/span&gt;.catch()&lt;span&gt;, &lt;/span&gt;.finally()&lt;span&gt; 로 등록한 모든 후속 콜백은 &lt;/span&gt;&lt;span&gt;&lt;b&gt;마이크로태스크 큐&lt;/b&gt;에 등록됩니다. &lt;/span&gt;&lt;span&gt;async function&lt;/span&gt; 내부의 &lt;span&gt;await&lt;/span&gt; 또한 &lt;span&gt;&lt;b&gt;결과를 꺼낼 때까지 함수 나머지&lt;/b&gt;&lt;/span&gt;를 마이크로태스크로 스케줄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;async/await 스케줄링&lt;/h3&gt;
&lt;pre id=&quot;code_1746431178586&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function foo() {
  console.log('A');
  await asyncOp();
  console.log('B');
}

// 위 async/await 코드는 내부적으로 다음과 같다.

function foo() {
  console.log('A');
  return asyncOp().then(() =&amp;gt; {
    console.log('B');
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;await asyncOp() 호출&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;asyncOp()&lt;span&gt;가 &lt;/span&gt;Promise&lt;span&gt;를 반환 &amp;rarr; &lt;/span&gt;.then(...)&lt;span&gt; 콜백 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;현재 &lt;span&gt;foo&lt;/span&gt; 함수는 즉시 반환되고, 그 반환값 역시 &lt;span&gt;Promise&lt;/span&gt;가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;현재 콜 스택 종료&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기 코드(여기서는 &lt;span&gt;console.log('A')&lt;/span&gt; 이후) 전부 실행 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 루프가 마이크로태스크 큐 확인&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;asyncOp().then(...)&lt;span&gt;에 등록된 콜백(&lt;/span&gt;console.log('B')&lt;span&gt;)이 &lt;/span&gt;&lt;span&gt;&lt;b&gt;마이크로태스크 큐&lt;/b&gt;&lt;/span&gt;&lt;span&gt;에 올라가 있는 상태&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마이크로태스크 큐에서 콜백 꺼내 실행&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호출 스택이 빈 즉시(&lt;span&gt;await&lt;/span&gt;가 비동기 작업임을 보장하듯) 이 콜백을 스택으로 옮겨 실행 &amp;rarr; &lt;span&gt;console.log('B')&lt;/span&gt; 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이후 매크로태스크(예: setTimeout 콜백) 처리로 넘어감&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의문점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모든 비동기 작업이 내부 스레드 풀을 직접 사용하는 것은 아닙니다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;15:1-15:88&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 예가 &lt;b&gt;Promise의 후속 처리 (.then, .catch, .finally)&lt;/b&gt; 와 &lt;b&gt;queueMicrotask()&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;17:1-19:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;17:1-17:190&quot;&gt;&lt;b&gt;Promise 또는 async/await:&lt;/b&gt; Promise 자체는 비동기 작업의 '상태'를 나타내는 객체입니다. Promise가 완료(resolve)되거나 거부(reject)되었을 때 실행될 콜백 함수들(.then, .catch, .finally)은 &lt;b&gt;마이크로태스크 큐(Microtask Queue)&lt;/b&gt;에 등록됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;18:1-19:0&quot;&gt;&lt;b&gt;queueMicrotask():&lt;/b&gt; 명시적으로 함수를 마이크로태스크 큐에 넣는 API입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;20:1-20:10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 차이&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;22:1-24:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;22:1-22:106&quot;&gt;&lt;b&gt;Web API (I/O, 타이머 등):&lt;/b&gt; 실제 &quot;대기&quot;나 &quot;작업&quot;은 백그라운드 스레드에서 이루어질 수 있습니다. 작업 완료 후 콜백이 태스크 큐 또는 마이크로태스크 큐에 추가됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;23:1-24:0&quot;&gt;&lt;b&gt;Promise 후속 처리 또는 queueMicrotask():&lt;/b&gt; 이들은 &lt;b&gt;JavaScript 엔진과 이벤트 루프의 스케줄링 메커니즘&lt;/b&gt;에 의해 관리됩니다. 콜백 함수들이 마이크로태스크 큐에 들어가고, 이벤트 루프는 현재 실행 중인 스크립트나 매크로태스크가 끝난 직후 &lt;b&gt;메인 스레드에서&lt;/b&gt; 마이크로태스크 큐를 비웁니다. 즉, 이 콜백 함수들은 백그라운드 스레드가 아니라 &lt;b&gt;메인 스레드&lt;/b&gt;에서 실행됩니다. (물론, Promise를 완료시킨 이벤트 자체는 백그라운드 스레드의 작업 결과일 수 있습니다. 예를 들어 Workspace가 완료되어 Promise가 resolve되는 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저마다 동작 방식이 다른가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;메인 스레드, 힙, 내부 스레드 풀, 이벤트 루프, 매크로태스크 큐(태스크 큐), 마이크로태스크 큐&lt;/b&gt;&lt;/span&gt; 같은 구성 요소와 동작 흐름은 &lt;span&gt;&lt;b&gt;크롬 V8 전용&lt;/b&gt;&lt;/span&gt;이 아니라, &lt;span&gt;&lt;b&gt;ECMAScript&lt;/b&gt;&lt;/span&gt; 및 &lt;span&gt;&lt;b&gt;HTML Living Standard&lt;/b&gt;&lt;/span&gt;에 정의된 &lt;span&gt;&lt;b&gt;표준 모델&lt;/b&gt;&lt;/span&gt;이기 때문에, &lt;span&gt;&lt;b&gt;모든 주요 브라우저 엔진&lt;/b&gt;&lt;/span&gt;에서 동일한 개념으로 구현됩니다. 다만 내부 최적화나 JIT, GC, 스레드 풀 크기 같은 세부 구현은 엔진마다 차이가 있을 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;플로우차트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 네트워크 요청 시 플로우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB6SmA/btsNK9O4ia5/9dkzWpam7JkI7nb5wQdnwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB6SmA/btsNK9O4ia5/9dkzWpam7JkI7nb5wQdnwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB6SmA/btsNK9O4ia5/9dkzWpam7JkI7nb5wQdnwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB6SmA%2FbtsNK9O4ia5%2F9dkzWpam7JkI7nb5wQdnwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;745&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. await Promise 시의 플로우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyaGsX/btsNK0Sjg4f/LpCKjyz83txtuEs9KER8hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyaGsX/btsNK0Sjg4f/LpCKjyz83txtuEs9KER8hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyaGsX/btsNK0Sjg4f/LpCKjyz83txtuEs9KER8hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyaGsX%2FbtsNK0Sjg4f%2FLpCKjyz83txtuEs9KER8hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;941&quot; height=&quot;730&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746430297446&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;메인 스레드 (Main thread) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&quot; data-og-description=&quot;메인 스레드(main thread) 는 브라우저가 사용자 이벤트와 페인트를 처리하는 곳입니다. 기본적으로, 브라우저는 단일 스레드를 사용하여 페이지의 모든 JavaScript를 실행하고 레이아웃, 리플로우 및&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cqM3rd/hyYPc0Pdz1/rdGEURgaSvYeZQbqZYCoUK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Main_thread&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cqM3rd/hyYPc0Pdz1/rdGEURgaSvYeZQbqZYCoUK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;메인 스레드 (Main thread) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;메인 스레드(main thread) 는 브라우저가 사용자 이벤트와 페인트를 처리하는 곳입니다. 기본적으로, 브라우저는 단일 스레드를 사용하여 페이지의 모든 JavaScript를 실행하고 레이아웃, 리플로우 및&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/618</guid>
      <comments>https://insengnewbie.tistory.com/618#entry618comment</comments>
      <pubDate>Mon, 5 May 2025 17:27:46 +0900</pubDate>
    </item>
    <item>
      <title>[React] React의 렌더링은 어떻게 이루어지는가? (1)</title>
      <link>https://insengnewbie.tistory.com/617</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RN을 배우기 시작하면서 React와 RN의 렌더링 간에 어떤 차이점이 있는지 궁금했고, 평소 React의 렌더링 방식에 무지했던 것 같아 궁금했던 점들을 하나씩 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;setState를 호출할 때마다 즉시 리렌더링이 발생하는가?&lt;/h2&gt;
&lt;pre id=&quot;code_1746098584717&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
      }}&amp;gt;+3&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. React는 state 업데이트를 하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다립니다. 이 때문에 리렌더링은 모든 setNumber() 호출이 완료된 이후에만 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Batching(배칭)이라고 합니다. Batching이란 여러 개의 상태 업데이트를 하나의 &quot;배치&quot;로 묶어서 처리함으로써, 불필요한 리렌더링을 방지하고 성능을 최적화하는 기법입니다. React에서는 상태가 바뀔 때마다 컴포넌트를 다시 렌더링하는데, 여러 번 연속으로 상태를 바꿀 경우 이를 하나의 렌더로 합치는 것이 배칭입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;업데이트 큐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746094496840&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
      }}&amp;gt;+3&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 내부에서는 함수 컴포넌트가 렌더될 때 훅들이 순서대로 Linked List 형태로 구성되는데, 각 훅 객체마다 queue 프로퍼티를 가지고 있습니다. 여기서 queue는 state 업데이트로 들어온 액션들을 모아두는 Circular Linked List입니다. 각 훅마다 서로 다른 업데이트 큐를 가지고 있기 때문에 업데이트는 절대 같은 큐에 섞이지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746096345021&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const hook: Hook = {
    memoizedState: null,

    baseState: null,
    queue: null,
    baseUpdate: null,

    next: null,
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState 함수에 전달하는 n =&amp;gt; n + 1을 업데이터 함수(Updater Function)라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;setNumber에 업데이트 함수가 전달되면, 업데이트 함수는 useState의 업데이트 큐에 쌓입니다.&lt;/li&gt;
&lt;li&gt;이벤트 핸들러 함수가 종료되고 리렌더링이 시작된 후 useState를 호출하면, React는 해당 useState의 업데이트 큐를 순회합니다.&lt;/li&gt;
&lt;li&gt;큐에 쌓인 순서대로 업데이트 함수를 꺼내서 실행하고 최종적으로 반환된 3이라는 결과를 useState에서 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buXBK1/btsNG0E58Q3/N4JWBBbzgRK1OxKcJRPos1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buXBK1/btsNG0E58Q3/N4JWBBbzgRK1OxKcJRPos1/img.png&quot; data-alt=&quot;공식문서의 예제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buXBK1/btsNG0E58Q3/N4JWBBbzgRK1OxKcJRPos1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuXBK1%2FbtsNG0E58Q3%2FN4JWBBbzgRK1OxKcJRPos1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;217&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공식문서의 예제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Virtual DOM과 Fiber는 어떤 차이가 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Virtual DOM&lt;/b&gt;은 실제 브라우저 DOM을 추상화한 자바스크립트 객체 트리입니다. React는 컴포넌트 렌더링 결과를 이 가상 DOM으로 먼저 표현하고, &lt;b&gt;이전 가상 DOM과 비교하여 최소한의 변경사항만 실제 DOM에 반영&lt;/b&gt;합니다. 이 과정을 통해 불필요한 레이아웃 계산과 스타일 재계산을 줄여 성능을 최적화합니다. 매 렌더마다 새로 생성되며, 이전 가상 DOM과 비교 후 폐기되는 라이프사이클을 가졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fiber 트리&lt;/b&gt;는 React 내부 자료구조로, &lt;b&gt;Virtual DOM의 업데이트 작업을 더 세밀하게 분할하고 스케줄링하기 위해&lt;/b&gt; 설계되었습니다. 각 Fiber 노드는 컴포넌트 1개와 대응하며, 이전 렌더의 props/state, 업데이트 큐, 자식/형제/부모 포인터 등을 가지고 있습니다. 랜더마다 재사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Virtual DOM은 &quot;무엇이 바뀌었는지&quot;를 계산하는 자료구조이고, Fiber 트리는 그 계산 작업을 &quot;언제, 어떻게&quot; 브라우저에 반영할지 스케줄링하는 엔진이라고 볼 수 있습니다.&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <category>react</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/617</guid>
      <comments>https://insengnewbie.tistory.com/617#entry617comment</comments>
      <pubDate>Thu, 1 May 2025 20:25:14 +0900</pubDate>
    </item>
    <item>
      <title>RN 공부를 다시 시작했다</title>
      <link>https://insengnewbie.tistory.com/616</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;즐겁구만&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/616</guid>
      <comments>https://insengnewbie.tistory.com/616#entry616comment</comments>
      <pubDate>Thu, 24 Apr 2025 13:47:47 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 의존성 그래프를 활용한 프로젝트 시각화 활용</title>
      <link>https://insengnewbie.tistory.com/615</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;당근 테크 블로그에서 발행한 &lt;i&gt;&quot;&lt;a href=&quot;https://medium.com/daangn/%EC%9D%98%EC%A1%B4%EC%84%B1-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EA%B0%81%ED%99%94-%EC%82%AC%EC%9D%B4%EB%93%9C-%EC%9D%B4%ED%8E%99%ED%8A%B8-%ED%95%9C%EB%88%88%EC%97%90-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0-eec17d5aabb2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;의존성 그래프를 활용한 프로젝트 시각화 - 사이드 이펙트 한눈에 파악하기&lt;/a&gt;&quot;&lt;/i&gt;를 보고 직접 적용해보았다. 글을 작성한 당근 개발자는 개발에 착수하기 전 일정을 산정하는데, 정작 작업하다가 예상치 못한 사이드 이펙트들로 일정을 지속적으로 미뤄지게 되었고, 이를 해결하고자 본 방법을 도입했다고 한다. 144개의 페이지가 존재하는 당근 웹에 비해 우리 웹의 규모가 작긴 하지만, 개발에 앞서 일정 산정 시 미처 고려하지 못했던 사이드 이펙트들로 인해 고생했던 경험에 공감했고 회사 프로젝트에서도 써먹어 볼 만하다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 아티클에서는 자세한 구현 및 로직을 알려주지는 않아서 Cursor AI를 활용해 원하는 의도를 명시적으로 전달 후 구현을 완료했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dependency Cruiser&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;당근과 마찬가지로 Dependency Cruiser를 선택했다. madge를 예전에 잠깐 사용해봤는데 graphviz에 의존성이 걸려있어서 조금 불편했던 기억이 있었고, 나도 Dependency Cruiser를 그대로 사용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어려웠던 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Dependency Cruiser의 옵션이 방대해서 원하는 옵션을 찾기가 어려웠다. Cursor AI에게 문서 링크를 넘겨주고 찾아보라고도 했는데, 옵션 이름을 틀리거나 없는 옵션을 만들어내는 등 화딱지가 나게 만들 뿐이었다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;특정 파일의 사이드 이펙트를 알기 위해서는 --reaches 옵션에 파일 경로를 넣어주어야 한다. 다른 TypeScript 파일들은 문제 없었는데, 특정 컴포넌트의 파일 경로를 인자로 넣으면 인식하지 못하는 문제가 있었다. 2시간 정도 씨름한 결과, --reaches 옵션에서는 파일 경로 상의 괄호를 인식하지 못하는 문제인 것을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/route-groups&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745246272570&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Route Groups | Next.js&quot; data-og-description=&quot;Route Groups can be used to partition your Next.js application into different sections.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b7FCPI/hyYFycA92O/4kEBj3AmEWuOh2AH0boZc1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/PhezF/hyYH8qN5Cb/OLOYYXcBsXWRX3bdKy3qa1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b7FCPI/hyYFycA92O/4kEBj3AmEWuOh2AH0boZc1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/PhezF/hyYH8qN5Cb/OLOYYXcBsXWRX3bdKy3qa1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Routing: Route Groups | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Route Groups can be used to partition your Next.js application into different sections.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Next.js App Router의 경우 파일 기반 라우팅을 지원하는데, 소괄호와 대괄호를 통해 여러 라우팅 옵션을 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;src/app/(sign)/login/page.tsx =&amp;gt; localhost:3000/login&lt;/li&gt;
&lt;li&gt;src/app/blog/[id]/page.tsx =&amp;gt; localhost:3000/blog/31&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 파일 경로에 속한 컴포넌트 파일 경로를 reaches 옵션에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;정상적으로 인식하지 못했다&lt;/span&gt;. 당근에서는 어떻게 처리했는지 모르겠지만 난 나만의 방식으로 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run-depcruise.js 라는 별도의 자바스크립트 스크립트 코드를 구현하여 Dependency Cruiser CLI를 실행하기 전에 파일 경로를 검증하고 replace하도록 처리했다. 이외에 여러 편리한 옵션을 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1745246571995&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 특수 문자 이스케이프 처리
let escapedFilePath = filePath;
if (filePath.includes('(') &amp;amp;&amp;amp; !filePath.includes('\\(')) {
  escapedFilePath = filePath.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
  console.log(`경로에 괄호가 있어 이스케이프 처리했습니다: ${escapedFilePath}`);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 run-depcruise.js를 간편하게 실행 가능하도록 package.json의 스크립트로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cruise&quot;:&amp;nbsp;&quot;node&amp;nbsp;scripts/run-depcruise.js&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예제는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745246708612&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Modal이 의존하고 있는 모듈 간의 의존 그래프, mermaid로 추출
npm run cruise --file=&quot;src/app/_component/Modal.tsx&quot; --mode=deps

# Modal에 의존하고 있는 모듈 간의 의존 그래프, png로 추출
npm run cruise --file=&quot;src/app/_component/Modal.tsx&quot; --mode=reaches --type=png&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mode
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deps: 지정된 컴포넌트가 어떤 파일에 영향을 받는지 그래프를 그린다.&lt;/li&gt;
&lt;li&gt;reaches: 지정된 컴포넌트가 어떤 파일에 영향을 미치는지, 얼만큼의 사이드 이펙트를 일으키는지 그래프를 그린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;type
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;png&lt;/li&gt;
&lt;li&gt;svg&lt;/li&gt;
&lt;li&gt;mmd: mermaid 가 이미지에 비해 용량이 적다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8zO2/btsNt4ALXoQ/JPAqHnoTioKmtWhikOvGl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8zO2/btsNt4ALXoQ/JPAqHnoTioKmtWhikOvGl1/img.png&quot; data-alt=&quot;useUserInfoStore에게 의존하는 파일들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8zO2/btsNt4ALXoQ/JPAqHnoTioKmtWhikOvGl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8zO2%2FbtsNt4ALXoQ%2FJPAqHnoTioKmtWhikOvGl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;162&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useUserInfoStore에게 의존하는 파일들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NG1Iv/btsNuFGWBcT/kkWMrAA0QyXZB9rboB5fi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NG1Iv/btsNuFGWBcT/kkWMrAA0QyXZB9rboB5fi0/img.png&quot; data-alt=&quot;useUserInfoStore가 의존하는 파일 및 모듈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NG1Iv/btsNuFGWBcT/kkWMrAA0QyXZB9rboB5fi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNG1Iv%2FbtsNuFGWBcT%2FkkWMrAA0QyXZB9rboB5fi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;599&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useUserInfoStore가 의존하는 파일 및 모듈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당근처럼 Github Actions를 이용해 Pull Request에 포함된 File Changes를 기준으로 영향이 가는 범위를 시각화하는 것도 해볼만 할 것 같다.&lt;/li&gt;
&lt;li&gt;사이드 이펙트를 확인할 때마다 파일 경로를 복사 붙여넣기 하기 너무 번거롭다. 이걸 차라리 VSCode Extension으로 개발해서 편리하게 쓰는 것도 좋을 듯?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a title=&quot;당근 테크 블로그 - 의존성 그래프를 활용한 프로젝트 시각화&quot; href=&quot;https://medium.com/daangn/%EC%9D%98%EC%A1%B4%EC%84%B1-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EA%B0%81%ED%99%94-%EC%82%AC%EC%9D%B4%EB%93%9C-%EC%9D%B4%ED%8E%99%ED%8A%B8-%ED%95%9C%EB%88%88%EC%97%90-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0-eec17d5aabb2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/daangn/%EC%9D%98%EC%A1%B4%EC%84%B1-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EA%B0%81%ED%99%94-%EC%82%AC%EC%9D%B4%EB%93%9C-%EC%9D%B4%ED%8E%99%ED%8A%B8-%ED%95%9C%EB%88%88%EC%97%90-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0-eec17d5aabb2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sverweij/dependency-cruiser&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sverweij/dependency-cruiser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <category>dependency cruiser</category>
      <category>next.js</category>
      <category>react</category>
      <category>의존성</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/615</guid>
      <comments>https://insengnewbie.tistory.com/615#entry615comment</comments>
      <pubDate>Mon, 21 Apr 2025 23:56:39 +0900</pubDate>
    </item>
    <item>
      <title>[React] useWebNotification</title>
      <link>https://insengnewbie.tistory.com/614</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC 유튜브에서 알림 설정을 해두니까 브라우저 웹 푸시 알림이 온 걸 보고 구현하게 되었다. 사용자에게 알림을 주고 싶을 때 유용한 것 같아 React에서 사용하기 쉽도록 구현했다. 만들어두면 회사나 개인 프로젝트에서라도 쓰겠지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Notification&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Notification&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useWebNotification&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅 &lt;span&gt;useWebNotification&lt;/span&gt;은 브라우저 Notification API를 React 에서 사용하기 쉽도록 래핑한 훅으로, 알림 권한 관리부터 알림 표시/닫기까지의 전 과정을 간편하게 처리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1745057170298&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useCallback, useEffect, useRef, useState } from 'react';

export type UseWebNotificationShow = (
  title: string,
  options?: NotificationOptions,
) =&amp;gt; Notification | undefined;

export interface UseWebNotificationReturn {
  isSupported: boolean /** 브라우저가 Notification API를 지원하는지 여부 */;
  show: UseWebNotificationShow;
  close: (notification?: Notification) =&amp;gt; void;
  closeAll: () =&amp;gt; void;
  ensurePermissions: () =&amp;gt; Promise&amp;lt;
    boolean | undefined
  &amp;gt; /** 권한을 요청하는 함수, true(권한 허용) 또는 false(거부) 반환 */;
  permission: NotificationPermission /** 현재 알림 권한 상태 */;
  permissionGranted: boolean /** 알림 권한이 granted인지 여부 */;
}

export function useWebNotification(
  autoRequest = true,
): UseWebNotificationReturn {
  const isSupported = typeof window !== 'undefined' &amp;amp;&amp;amp; 'Notification' in window;
  const notificationsRef = useRef&amp;lt;Set&amp;lt;Notification&amp;gt;&amp;gt;(new Set());
  const [permission, setPermission] = useState&amp;lt;NotificationPermission&amp;gt;(
    isSupported ? Notification.permission : 'default',
  );
  const permissionGranted = permission === 'granted';

  // 권한 변경 감지
  useEffect(() =&amp;gt; {
    if (!isSupported) return;

    const updatePermission = () =&amp;gt; {
      setPermission(Notification.permission);
    };

    // 최신 브라우저에서 권한 변경을 감지
    if ('permissions' in navigator &amp;amp;&amp;amp; navigator.permissions) {
      navigator.permissions
        .query({ name: 'notifications' as PermissionName })
        .then((status) =&amp;gt; {
          status.addEventListener('change', updatePermission);
          return () =&amp;gt; {
            status.removeEventListener('change', updatePermission);
          };
        })
        .catch((error) =&amp;gt; {
          console.error('Failed to query notification permission:', error);
        });
    }
  }, [isSupported]);

  // 권한 요청 함수
  const ensurePermissions = useCallback((): Promise&amp;lt;boolean | undefined&amp;gt; =&amp;gt; {
    if (!isSupported) {
      return Promise.resolve(undefined);
    }

    if (permission === 'granted') {
      return Promise.resolve(true);
    }

    return Notification.requestPermission()
      .then((newPermission) =&amp;gt; {
        setPermission(newPermission);
        return newPermission === 'granted';
      })
      .catch((error) =&amp;gt; {
        console.error('Failed to request notification permission:', error);
        return false;
      });
  }, [isSupported, permission]);

  // 알림을 표시하는 함수
  const show: UseWebNotificationShow = useCallback(
    (
      title: string,
      options?: NotificationOptions,
    ): Notification | undefined =&amp;gt; {
      if (!isSupported) {
        console.warn('Web Notification is not supported in this browser.');
        return undefined;
      }
      if (permission !== 'granted') {
        console.warn('Notification permission is not granted.');
        return undefined;
      }

      try {
        const notification = new Notification(title, options);
        notificationsRef.current.add(notification);

        // 알림이 닫힐 때 Set에서 제거
        notification.addEventListener('close', () =&amp;gt; {
          notificationsRef.current.delete(notification);
        });

        return notification;
      } catch (error) {
        console.error('Failed to create notification:', error);
        return undefined;
      }
    },
    [isSupported, permission],
  );

  // 특정 알림 또는 모든 알림을 닫는 함수
  const close = useCallback((notification?: Notification) =&amp;gt; {
    if (notification) {
      notification.close();
      notificationsRef.current.delete(notification);
    }
  }, []);

  // 모든 알림을 닫는 함수
  const closeAll = useCallback(() =&amp;gt; {
    for (const notification of notificationsRef.current) {
      notification.close();
    }
    notificationsRef.current.clear();
  }, []);

  useEffect(() =&amp;gt; {
    if (autoRequest &amp;amp;&amp;amp; isSupported &amp;amp;&amp;amp; permission === 'default') {
      ensurePermissions();
    }

    // 컴포넌트가 언마운트될 때 모든 알림 정리
    return () =&amp;gt; {
      closeAll();
    };
  }, [autoRequest, isSupported, permission, ensurePermissions, closeAll]);

  return {
    isSupported,
    show,
    close,
    closeAll,
    ensurePermissions,
    permission,
    permissionGranted,
  };
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT/Front-End</category>
      <category>react</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/614</guid>
      <comments>https://insengnewbie.tistory.com/614#entry614comment</comments>
      <pubDate>Sat, 19 Apr 2025 19:16:51 +0900</pubDate>
    </item>
    <item>
      <title>[독서] 최선의 고통</title>
      <link>https://insengnewbie.tistory.com/613</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yrGBM/btsNlBLNoPa/EP1GzrdXZpApeC2jFNuKW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yrGBM/btsNlBLNoPa/EP1GzrdXZpApeC2jFNuKW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yrGBM/btsNlBLNoPa/EP1GzrdXZpApeC2jFNuKW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyrGBM%2FbtsNlBLNoPa%2FEP1GzrdXZpApeC2jFNuKW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;679&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책을 고른 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;나는 뇌과학자 장동선 박사님의 유튜브를 즐겨본다. 많은 현대인들이 조금씩이나마 가지고 있는 정신적인 문제들을 현대 사회에서 어떻게 풀어갈 수 있는지 재미있게 설명해주신다. 특히 마음에 와닿았던 구절이 하나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;고통을 감수하면서까지 내가 하는 일은 무엇인가?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;최선의 고통&quot;과 &quot;도파민네이션&quot;이라는 두 권의 책으로 &quot;삶의 고통&quot;에 대해서 풀어준다. 그 중에서도 &quot;최선의 고통&quot; 이라는 책이 궁금했고, 그렇게 읽어보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;저자인 폴 블룸은 &quot;최선의 고통&quot;에서 명쾌한 정답을 제시하지 않는다. 고통이란 좋은 것인지 나쁜 것인지, 우리가 선택할 수 없었던 고통에 어떻게 대응해야 하는게 옳은지 알려주지는 않는다. 다만 생각해볼 꺼리를 던져준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선택적 고난&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;내가 선택해서 받는 고통은 내 삶에 가치를 더해준다. 공부를 하기 위해 책상 앞에서 머리를 쥐어뜯으며 공부하는 것을 포함해서, 공포영화를 즐겨보거나, 마라톤을 달리거나, 에베레스트 산을 오르는 일들은 처음부터 끝까지 고통스러운 일 투성이겠지만 커다란 즐거움으로도 이어질 수 있다. 고난이 명예로 이어질 수도 있고, 관계를 개선할 수도 있고, 기억으로서 소비될 수도 있다. 이는 의미 있다고 여기는 경험의 필수 요소다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;얼마 전 다녀왔던 경주 마라톤에서 10km를 달리는 내내 다리가 삐그덕거리고 발바닥이 터질 것 처럼 아파와서 괴로웠지만, 결국 완주하여 내 삶의 업적이 추가된 듯한 기분과 고난을 마무리했다는 만족감과 마라톤에서의 기억을 얻을 수 있었다. 이 기억은 글을 쓰는 이 순간에도 조금씩 내 삶에 가치를 더해주고 있는 듯한 기분이 들게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;삶의 '이유'가 있는 사람은 거의 모든 삶의 '양상'을 견딜 수 있다&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;홀로코스트의 생존자이자, 유대인 심리학자인 빅터 프랭클은 위와 같은 말을 남겼다. 홀로코스트 당시 수용소 내에서 폭넓은 삶의 목적이 있거나 목표나 관계 등 살아야할 이유가 있는 사람들이 오래 생존했다고 한다. 전쟁이 끝난 후 빅터 프랭클은 모든 것을 잃었지만, 그래도 삶을 재건하여 의미와 기쁨으로 가득한 풍요로운 삶을 살았다고 한다. &quot;살아야 할 이유가 있으면 어떤 고통도 감내할 수 있다&quot;.. 홀로코스트와 같이 내가 선택할 수 없으면서 내 삶의 모든 것을 앗아가버리는 고난을 갑작스럽게 찾아왔을 때 나는 내 삶의 의미를 찾을 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;몰입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;미하이 칙센트미하이에게 몰입은 어느 순간 완전히 빠져드는 강렬한 집중의 경험이다.&quot; 폴 블룸은 &quot;좋은 삶을 만드는 것을 무엇일까?&quot;라는 의문을 제기한다. 칙센트미하이는 직업이 몰입의 원천이면서 몰입이 좋은 삶의 특징이 될 수 있다고 말한다. 그러나 폴 블룸은 몰입의 한계를 지적한다. 올바른 일이든 올바르지 않은 일이든 몰입에 빠질 수 있다. 저자는 삶의 목적과 의미에 대해서 질문하기 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;불행과 고난을 통해 현실을 규정한다.&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;저자는 일정한 정도의 불행과 고난이 풍요롭고 의미 있는 삶에 필수적이라고 말한다. 애시당초 내가 제어할 수 없는 세상 속에서 내가 감당할 수 없는 불행과 고난이 닥치는 것은 피할 수 없는 일이라고 생각한다. 고통 속에서 삶의 의미를 찾고, 고난을 개선해나가며 만족을 느낄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그리고 인간은 고난을 선택하기도 한다. 에베레스트 산을 오르거나 공포 영화를 보거나 BDSM 같은 성적 취향을 가지거나 열심히 공부하며 몰입하기도 한다. 인간은 행복하기 위해 고난을 선택하는걸까? 고통과 고난 그 자체에도 의미가 있지 않을까? 고통과 고난이 없는 평온한 삶은 죽어있는 것과 다르지 않지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다만 여기서 주의해야 할 점은 모든 고난에 의미가 있지는 않다는 점이다. 저자는 많은 사람들이 자신이 겪은 고난에 의미가 있다고 &quot;믿고 싶어한다&quot;고 말한다. 지갑 속에서 오만원짜리 지폐를 잃어버리는 것은 정말 가슴 아픈 일이지만, 의미가 있지는 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;의미는 중요하고 영향력 있는 목표에 대한 추구를 포함한다.&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 불가피하게 난관, 불안, 갈등 그리고 어쩌면 더 많은 고난을 수반할 수 밖에 없다. 고난에 의미가 따라올 수도 있지만, 의미 있는 여정에는 언제나 고난이 뒤따른다. 하지만 의미가 있기에 고난을 견딜 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여러 리뷰에서 &quot;글을 장황하게 풀어냈지만 결론이 없어서 별로&quot;라는 평이 많았지만, 나는 오히려 나에게 생각해볼 수 있는 꺼리를 던져주어서 좋았다. 삶의 의미, 고통의 의미.. 그런 것은 애시당초 한 문장으로 정리할 수 있는 단순한 것이 아니라는 것을 느꼈다. 책의 저자인 폴 블룸은 결론적으로 다음 세 가지 주장을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 특정한 유형의 선택적 고난은 기쁨의 근원이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 잘 살아낸 삶은 쾌락적 삷보다 더 많은 의미를 지닌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 고생과 난관을 거쳐야 하는 고난은 고귀한 목적을 이루는 한편 완전하고 충만한 삶을 사는 데 필수적인 요소다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 나에게 있어서 잘 살아낸 삶이란 뭘까? 나에게는 어떤 고통을 감수하면서까지 추구하고 싶은 의미가 있나? 아직 명확한 답변을 스스로 내놓지는 못하겠다. 다만 적어도 내가 선택한 고통과 고난을 회피하지는 않을 것이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>자기계발/독서</category>
      <category>독서</category>
      <category>최선의고통</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/613</guid>
      <comments>https://insengnewbie.tistory.com/613#entry613comment</comments>
      <pubDate>Wed, 16 Apr 2025 02:59:56 +0900</pubDate>
    </item>
    <item>
      <title>20250405 경주 벚꽃 마라톤 10K 완주</title>
      <link>https://insengnewbie.tistory.com/612</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc66aV/btsNa8bPnoh/Avk6SqeRk9AmCoFiSSv7L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc66aV/btsNa8bPnoh/Avk6SqeRk9AmCoFiSSv7L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc66aV/btsNa8bPnoh/Avk6SqeRk9AmCoFiSSv7L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc66aV%2FbtsNa8bPnoh%2FAvk6SqeRk9AmCoFiSSv7L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;306&quot; height=&quot;461&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인생 첫 마라톤에 참여하고 왔다. 10km일 뿐이긴 하지만 러닝과는 일절 관계없는 삶을 살아온 나에게는 엄청나게 긴 거리였기 때문에, 완주했다는 것만으로도 만족감을 느끼고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참여하기로 마음먹은 동기는 별거 없다. 작년 말쯤 사내 러닝동호회에서 벚꽃이 만개한 날에 마라톤 참여한다는 소식을 듣고, 되게 낭만적일 것 같다고 생각(착각)했다. 마침 입사 동기들도 관심있어 하길래 같이 참가하기로 했다. 그래도 사무직 체력으로 하프 마라톤을 뛰기는 부담스러워서 10km로 신청했던 기억이 난다. 그렇게 마라톤을 신청하고 나서 그 존재를 까맣게 잊은 채 몇 개월의 시간을 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1년째 사무직 생활을 유지 중인 내가 10km를 완주할 수 있었던 건, 한 달 전부터 복싱을 시작한게 큰 것 같다. 헬스보다 유산소의 비중이 훨씬 높은 복싱 덕택에 그나마 신체 컨디션과 체력을 끌어올린 채로 마라톤을 뛸 수 있었고, 결국 완주할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds0SY6/btsNamom3lS/rCdy6qlSku3jqgkC1YrAQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds0SY6/btsNamom3lS/rCdy6qlSku3jqgkC1YrAQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds0SY6/btsNamom3lS/rCdy6qlSku3jqgkC1YrAQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds0SY6%2FbtsNamom3lS%2FrCdy6qlSku3jqgkC1YrAQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;266&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;1104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아무래도 마라톤이 처음이다 보니 페이스 분배 같은건 개나주고 막 달렸다. 초반 0.5km 까지는 남들에게 뒤쳐지면 안 된다는 생각에 5분 30초 페이스로 달렸는데, 생각보다 너무 힘들어서 중간에 조금 걷고 다시 달리고를 반복했다. 지금 생각하면 왜 그랬지 싶은데 달릴 때는 남들 추월하려고 빨리 달리다가 금방 힘들어져서 또 걷고, 체력이 돌아오면 다시 추월하려고 빨리 달리고를 반복했다. 결과적으로 7분 18초의 페이스로 마무리했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8YRbb/btsNbNrkxad/plASLudIl2sXKkfkhkk7e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8YRbb/btsNbNrkxad/plASLudIl2sXKkfkhkk7e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8YRbb/btsNbNrkxad/plASLudIl2sXKkfkhkk7e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8YRbb%2FbtsNbNrkxad%2FplASLudIl2sXKkfkhkk7e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;623&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래도 마라톤 중간중간 걸어서 좋았던 점이 있는데, 만개한 벚꽃을 나긋이 감상할 수 있었다는 점이다. 막 달릴 때는 몰랐는데 보문호를 둘러싸고 펼쳐진 벚꽃나무 숲을 바라보는 일은 꽤나 기분이 좋았다. 평소 업무나 성장에 대해 여러 생각이 머릿속을 헤집으며 항상 머리가 복잡했는데, 마라톤을 달리는 동안에는 정말 모든 잡생각을 떨쳐내고 달릴 수 있었다. 그냥 너무 힘들어서 딴생각을 못했던 것도 맞고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;지금 나는 카페에서 이 글을 작성하고 있다. 근육통은 익숙해서 별 문제는 없는데, 달릴 때 오른쪽 발로 너무 힘차게 박찼던게 문제인지 오른쪽 무릎 관절이 조금 아프다. 그리고 고관절도 조금 땡기는 느낌. 특히 숨쉬기가 힘들어서 입으로 숨을 쉬었더니 아직도 목에 좀 가래낀거 같은 느낌이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvy9NS/btsNbqpB698/HcCE7A5ewMxnk8e5yBretk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvy9NS/btsNbqpB698/HcCE7A5ewMxnk8e5yBretk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvy9NS/btsNbqpB698/HcCE7A5ewMxnk8e5yBretk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvy9NS%2FbtsNbqpB698%2FHcCE7A5ewMxnk8e5yBretk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;572&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;지금 읽고 있는 책 중에 &quot;최선의 고통&quot;이라는 책이 있다. &quot;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;왜&lt;span style=&quot;color: #000000;&quot;&gt; 사람들은 &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;에베레스트산 정상을 끝끝내 오르고, 목숨을 내걸어야 하는 전쟁터에 자원입대하고, 타인을 돕는 일에 평생을 던지는가&quot;에 대해 다루는 책이다. 달리는 도중에는 숨이 턱끝까지 차오르고 허벅지가 터질듯이 괴로웠지만, 남이 시켜서 한게 아닌 내가 선택한 고통이었기에 끝까지 감내할 수 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기회가 된다면 다음 마라톤에도 참여해보고 싶다. 하프는 아직 무리이고 10km에 익숙해지고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIWCF/btsNabgoQf2/6Krn6WTkcP9J2ouaQoBUh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIWCF/btsNabgoQf2/6Krn6WTkcP9J2ouaQoBUh0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1050&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;42.86&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.3588%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIWCF/btsNabgoQf2/6Krn6WTkcP9J2ouaQoBUh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIWCF%2FbtsNabgoQf2%2F6Krn6WTkcP9J2ouaQoBUh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVhaWw/btsNbaHg6yC/jY1Xj8jF628l5aFZKijjY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVhaWw/btsNbaHg6yC/jY1Xj8jF628l5aFZKijjY0/img.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.4784%;&quot; data-widthpercent=&quot;57.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVhaWw/btsNbaHg6yC/jY1Xj8jF628l5aFZKijjY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVhaWw%2FbtsNbaHg6yC%2FjY1Xj8jF628l5aFZKijjY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>혼잣말</category>
      <category>마라톤</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/612</guid>
      <comments>https://insengnewbie.tistory.com/612#entry612comment</comments>
      <pubDate>Sun, 6 Apr 2025 15:21:58 +0900</pubDate>
    </item>
    <item>
      <title>사람을 만나고 싶다</title>
      <link>https://insengnewbie.tistory.com/611</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스스로 무엇을 하고 싶은지 아직 명확하지는 않다. 우선 책을 많이 읽고 나의 현재 심리 상태를 정리하는 시간을 가지고 싶다. 그 이후에는 블로그 글쓰기 모임이든 독서 모임이든 사람을 만나고 싶다. 기왕이면 개발 관련으로. 부산에서 오프라인 커뮤니티를 만들어보고 싶기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743695618550&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 1~10기. 7년의 커뮤니티 운영 회고&quot; data-og-description=&quot;이 글은 개발자 글쓰기 모임 - 글또를 7년 동안 운영하며 배운 것을 회고하기 위한 목적으로 작성했습니다 키워드 : 글또, 글또 회고, 커뮤니티 운영&quot; data-og-host=&quot;zzsza.github.io&quot; data-og-source-url=&quot;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&quot; data-og-url=&quot;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cQDIRE/hyYxPzCpa6/1DkMk6uUupBswvbJnjpXH0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zzsza.github.io/diary/2025/03/30/geultto-operation-retrospective/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cQDIRE/hyYxPzCpa6/1DkMk6uUupBswvbJnjpXH0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 1~10기. 7년의 커뮤니티 운영 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 개발자 글쓰기 모임 - 글또를 7년 동안 운영하며 배운 것을 회고하기 위한 목적으로 작성했습니다 키워드 : 글또, 글또 회고, 커뮤니티 운영&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;zzsza.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;어제 누군가 7년 동안의 글또 커뮤니티를 운영한 경험을 작성한 회고를 읽게 되었다. 읽는 동안 되게 멋있다는 생각이 들었다. 7년이라는 기간 동안 무언가에 열정을 쏟았다는 점이 그렇고, 스스로의 일에 자부심과 책임감을 가지고 몰입할 수 있었다는 점이 그렇다. 나도 이런 무언가를 찾아보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일단 내일 경주에서 열리는 벚꽃 마라톤에 다녀올 예정이다. 첫 마라톤이라 가볍게 10km로 신청했는데 어떨지 잘 모르겠다. 온갖 잡생각을 잠시 내려놓고 완주하는 것이 목표이다.&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/611</guid>
      <comments>https://insengnewbie.tistory.com/611#entry611comment</comments>
      <pubDate>Fri, 4 Apr 2025 01:01:58 +0900</pubDate>
    </item>
    <item>
      <title>마음 속 열기가 사라진 기분이다</title>
      <link>https://insengnewbie.tistory.com/610</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;무언가 삶을 불태울 수 있게 해주는 것을 잃어버린 기분이다. 지금의 나에게는 어떤 고통을 겪어도 이루고 싶은 무언가가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반 년 전까지만 해도 회사에 기여하거나 개발자 커리어를 쌓기 위해 노력하는데에 즐거움을 느꼈다. 매일같이 야근하고 주말에도 카페에서 코딩을 하거나 강의를 보며 공부했다. 그때 당시에는 그렇게 하는게 즐거웠다. 매일매일 성장하는게 즐거웠고 회사에 기여하면서 또는 나 자신의 커리어를 쌓아나가는게 좋았다. 운동이나 게임, 취미 같은 것을 내팽겨치고 나를 오로지 몰두하게 한 것은 &quot;마음 속 열기&quot;였던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;열기&quot;, &quot;열정&quot;, &quot;동기부여&quot; 등등. 나를 어떤 일에 오로지 몰입하게 만들고 무언가를 하고 싶게 만드는 원동력이다. 지금의 나에게는 이러한 원동력이 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;종종 스스로의 삶이 무채색이라고 느낄 때가 있다. 나는 지금 다채로운 삶을 살고 있는 것일까? 나에게는 어떠한 고통을 겪어도 이루고 싶은 꿈이 있나? 나는 무엇을 하고 싶은 사람인가? 업계에서 유명한 사람이 되거나 오픈소스 기여자가 된다거나 강의를 만들어 보고 싶다는 생각은 있지만, &quot;꿈&quot;이라고 부를 정도로 강하게 바라는 욕망은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인생에는 가끔 &quot;노잼시기&quot;가 찾아온다고 한다. 유튜브 장동선 박사님의 영상 중 나오는 말이다. 나의 현재 상태가 특별한 일은 아니고 누구에게나 찾아올 수 있는 시기인 것 같다. 나의 경우에는 &quot;시들하다&quot;기 보다는 &quot;무료하다&quot;는 표현이 적합하다고 생각하지만. 그래서 당장 해결될 문제도 아닌 것 같고, 천천히 책을 읽어보려고 한다.&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/610</guid>
      <comments>https://insengnewbie.tistory.com/610#entry610comment</comments>
      <pubDate>Mon, 31 Mar 2025 01:11:42 +0900</pubDate>
    </item>
    <item>
      <title>[React] useOnClickOutside</title>
      <link>https://insengnewbie.tistory.com/609</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달 또는 팝업창을 닫을 때 외부 영역 클릭 이벤트를 감지해야 하는데, 그 때마다 아래와 같은 코드를 작성해야 한다. 똑같은 코드를 매번 작성하는 것은 정말 번거로운 일이다. 그래서 커스텀 훅으로 분리하여 재사용한다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  const containerRef = useRef(null);

  useEffect(() =&amp;gt; {
    function handleClickOutside(e) {
      if (containerRef.current &amp;amp;&amp;amp; !containerRef.current.contains(e.target)) {
        setIsBankListOpen(false);
      }
    }

    document.addEventListener('mousedown', handleClickOutside);
    return () =&amp;gt; {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useOnClickOutside&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { RefObject, useEffect } from 'react';

export default function useOnClickOutside&amp;lt;T extends HTMLElement&amp;gt;(
  ref: RefObject&amp;lt;T | null&amp;gt;,
  handler: (event: MouseEvent | TouchEvent) =&amp;gt; void,
) {
  useEffect(() =&amp;gt; {
    const listener = (event: MouseEvent | TouchEvent) =&amp;gt; {
      // ref.current가 없거나, 클릭한 대상이 ref 내부에 있다면 아무 작업도 수행하지 않음
      if (!ref.current || ref.current.contains(event.target as Node)) {
        return;
      }
      handler(event);
    };

    // mousedown, touchstart 이벤트를 감지
    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);

    // 컴포넌트 unmount 시 이벤트 리스너 제거
    return () =&amp;gt; {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT/Front-End</category>
      <category>react</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/609</guid>
      <comments>https://insengnewbie.tistory.com/609#entry609comment</comments>
      <pubDate>Tue, 25 Mar 2025 15:29:56 +0900</pubDate>
    </item>
    <item>
      <title>[FE] 프론트엔드 서비스 최적화? 토스에서는 이렇게 합니다! - 정리</title>
      <link>https://insengnewbie.tistory.com/608</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하면 좋을 것 같은데? 싶을 땐 하지마라!&lt;/li&gt;
&lt;li&gt;해야 될 것 같으면 한 번만 더 생각해라!&lt;/li&gt;
&lt;li&gt;이제 진짜 해야 된다 싶으면 프로파일링(측정)부터 시작하자.&lt;/li&gt;
&lt;li&gt;그래도 토스에서 성능 최적화 시점이 좀 늦는 것 같기는 하다고 한다. by 토스 프론트 리드&lt;/li&gt;
&lt;li&gt;개발자들은 늘 최적의 환경에서 테스트하기 때문에 실제로 불편함을 느끼는 사용자들을 발견하고 나서야 대처하게 된다.&lt;/li&gt;
&lt;li&gt;세상 모든 것은 트레이드오프가 있다.&lt;/li&gt;
&lt;li&gt;&amp;rArr; 최적화를 위해 시간을 줄였다면 어떤 것을 희생했다는 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;프론트 최적화의 2가지 측면&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초기 로딩 속도가 빠른 것&lt;/li&gt;
&lt;li&gt;런타임에서 반응성이 빠르고 좋음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;초기 로딩 속도가 느린 이유&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;대역폭&lt;/li&gt;
&lt;li&gt;지연 시간&lt;/li&gt;
&lt;li&gt;자바스크립트 실행 시간&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 대역폭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대역폭 : 쌀가마니를 옮기는 트럭의 숫자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 지연시간&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지연시간 : 쌀가마니를 옭기는 트럭의 속도&lt;/li&gt;
&lt;li&gt;캐싱 활용&lt;/li&gt;
&lt;li&gt;이동할 것 같은 페이지 프리페치&lt;/li&gt;
&lt;li&gt;적절히 번들 쪼개기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;런타임에서 반응성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 로딩보다는 로딩 이후 반응이 빠르고 좋아야함&lt;/li&gt;
&lt;li&gt;어떻게 런타임 성능을 측정할 것인가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FPS&lt;/li&gt;
&lt;li&gt;상태 변화 시 얼마나 많은 렌더링이 일어나는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;드래그앤드랍 시 60프레임을 유지하는가? 등의 기준을 세움&lt;/li&gt;
&lt;li&gt;실제 동작 시간이 더 오래 걸리더라도 반응성이 좋으면 사용자는 성능이 좋다고 느낌 &amp;rArr; 사용자를 속이기&lt;/li&gt;
&lt;li&gt;메모를 사용해서 리렌더링을 최적화하더라도, 한 컴포넌트에 여전히 많은 컴포넌트가 포함되어 있으면 한 번의 렌더링에도 많은 연쇄적인 일이 발생한다.&lt;/li&gt;
&lt;li&gt;&amp;rArr; 꼭 필요한 부분만 리렌더링되도록 함&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/608</guid>
      <comments>https://insengnewbie.tistory.com/608#entry608comment</comments>
      <pubDate>Sun, 9 Mar 2025 14:57:59 +0900</pubDate>
    </item>
    <item>
      <title>할게 많아서 정신이 없다</title>
      <link>https://insengnewbie.tistory.com/607</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;회사 일은 회사 일대로 적용해보고 싶은거나 개선해보고 싶은 것이 많고, 개인적으로도 배우고 싶거나 시도해보고 싶은게 많다. 머릿속이 정리가 되지 않는 기분이다. 해보고 싶거나 해야할 게 너무 많아서 오히려 무엇부터 시작해야 할지 헤매는 중인 것 같다. 그래도 이럴 때가 가끔 있어서 해결책은 대강 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해야하거나 하고 싶은 일의 리스트를 작성한 후, 나의 기분, 여유 시간 등의 상황을 고려해서 일의 우선순위를 매긴다. 그리고 상위 리스트 중에서 지금 내키는 일에 집중한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 글을 쓰는 것도 내가 지금 무엇을 할지 스스로 정리하고자 글로써 작성해보는 것이다.&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/607</guid>
      <comments>https://insengnewbie.tistory.com/607#entry607comment</comments>
      <pubDate>Sun, 9 Mar 2025 14:44:07 +0900</pubDate>
    </item>
    <item>
      <title>[FE] 번들 다이어트에 대한 타사 사례 조사</title>
      <link>https://insengnewbie.tistory.com/606</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우리가 개발하고 있는 웹 프로젝트의 로드 속도가 느린 것 같다는 피드백을 듣고 개선 작업에 착수했다. 웹 로드 속도를 개선하기 위해 각자 서칭 후 팀원들과 여러 의견을 나누었고, 아키텍처 개선/인프라 개선/번들 최적화 등 다양한 아이디어가 나왔다. 그 중에서 우리는 &lt;b&gt;번들 다이어트&lt;/b&gt;가 우리 프로젝트에서 빠르게 효과를 볼 수 있으면서 적용하기 쉬울거라 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번 글은 타사의 번들 다이어트 사례를 조사하면서 알게 된 내용을 정리한 것이다. 번들 크기는 로드 속도에 직접적으로 영향을 끼치는 만큼 이미 여러 회사에서 해당 이슈를 해결하기 위해 여러 방법을 시도했고, 그 경험을 각종 컨퍼런스나 아티클로 공유하고 있어서 지식을 얻기 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;토스 - JavaScript Bundle Diet&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;토스 슬래시 21에서 발표된 세션이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;node_modules의 중복 라이브러리 줄이기 &amp;rArr; npm dedupe&lt;/li&gt;
&lt;li&gt;npm 보다 효율적인 yarn 사용 &amp;rArr; yarn dedupe&lt;/li&gt;
&lt;li&gt;lodash 최적화 &amp;rArr; 사용하는 것만 import 하도록 바벨 플러그인 사용 또는 필요한 코드만 직접 구현해서 쓰기도 함&lt;/li&gt;
&lt;li&gt;번들포비아라는 플랫폼에서 라이브러리 정보 상세 확인&lt;/li&gt;
&lt;li&gt;Exports Analysis : 함수별로 얼마나 용량을 차지할지 미리 확인&lt;/li&gt;
&lt;li&gt;사내 라이브러리 제작 : 트리셰이킹 &amp;larr; 당장 우리한테는 필요없는 이야기&lt;/li&gt;
&lt;li&gt;무거운 라이브러리의 영향 줄이기 : 몇 페이지에서만 라이브러리를 사용했지만, 모든 페이지에서 용량이 증가함 &amp;rArr; next.js에서 처리해줌 또는 next/dynamic + webpack magic comment (prefetch 기능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네이버 - 번들사이즈 최소화를 통한 웹 성능 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;네이버 컨퍼런스 Dan 24에서 발표된 세션이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 프론트엔드 아키텍처 사용 중&lt;/li&gt;
&lt;li&gt;크롬 개발자 모드의 커버리지 : 사용 중인 코드를 파악&lt;/li&gt;
&lt;li&gt;어떤 상황에서도 사용되지 않는 JS 리소스 식별&lt;/li&gt;
&lt;li&gt;커버리지에 포함되지 않는 코드라도 특정 상황에서는 사용될 수 있음&lt;/li&gt;
&lt;li&gt;사용되지 않는 JS 리소스가 번들에 포함되는 이유 분석&lt;/li&gt;
&lt;li&gt;가능한 많은 코드를 삭제하여 꼭 필요한 코드만 포함되도록 설정하자!&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;불필요한 polyfill 제거 &amp;rArr; browserslist-config&lt;/li&gt;
&lt;li&gt;svg와 lottie json 파일 내부의 data:image/png; 이미지를 sharp로 압축
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드에 숨어있는 이미지를 sharp를 활용하여 최대한 손실없이 압축&lt;/li&gt;
&lt;li&gt;압축 프로세스를 eslint-plugin으로 자동화하여 DX 및 UX 향상&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;불필요하게 포함된 라이브러리(vconsole)를 프로덕션에서 제거하기&lt;/li&gt;
&lt;li&gt;네이티브 기능 적극 활용하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;html-entities &amp;rArr; DomParser&lt;/li&gt;
&lt;li&gt;axios &amp;rArr; native fetch&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;lodash를 자체 구현
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트리쉐이킹 하는데 허들이 되는 구조&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;상태 관리 라이브러리 자체 구현&lt;/li&gt;
&lt;li&gt;npm 라이브러리의 효율적인 관리
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;npm dedupe로 중복 라이브러리 제거하기&lt;/li&gt;
&lt;li&gt;npm 라이브러리 취약점 감지 및 최신화하기&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;타사의 사례를 찾아보고 다른 최적화 관련된 아티클도 여럿 읽으면서 드는 생각은 이제 번들 다이어트, 번들 최적화도 특별한 기술은 아니구나.. 싶었다. 웹의 역사가 깊은 만큼 최적화에 대한 방법론이나 최적화의 경험도 많이 공유되었고, 할 수 있는 방법도 거의 고정된 느낌이다. 그래도 자신의 상황, 능력을 인지하고 자신에게 맞는 방법을 찾아서 적용하는 것은 우리의 몫인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://toss.im/slash-21/sessions/3-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://toss.im/slash-21/sessions/3-2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1740816059520&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JavaScript Bundle Diet&quot; data-og-description=&quot;웹사이트 기능이 많아짐에 따라 점점 커지는 JavaScript Bundle Size. 웹사이트 속도는 느려지고, 잠재 고객을 잃을 수도 있습니다. 라이브러리를 사용하는 그리고 제공하는 관점에서 Bundle Size가 커질 &quot; data-og-host=&quot;toss.im&quot; data-og-source-url=&quot;https://toss.im/slash-21/sessions/3-2&quot; data-og-url=&quot;https://toss.im/slash-21/sessions/3-2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dOV6K8/hyYm1ys1oJ/GQMFEKTuFkKraQJO6ebRSK/img.png?width=1280&amp;amp;height=720&amp;amp;face=726_156_858_300,https://scrap.kakaocdn.net/dn/lYe5o/hyYm6Gyn58/4zYXBlDYKAw9vLwE6Lcg71/img.png?width=1280&amp;amp;height=720&amp;amp;face=726_156_858_300,https://scrap.kakaocdn.net/dn/dx7Jf4/hyYjxeCzEI/Cjec3r4NTmi4WQXyQknDHK/img.png?width=840&amp;amp;height=996&amp;amp;face=339_208_530_416&quot;&gt;&lt;a href=&quot;https://toss.im/slash-21/sessions/3-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.im/slash-21/sessions/3-2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dOV6K8/hyYm1ys1oJ/GQMFEKTuFkKraQJO6ebRSK/img.png?width=1280&amp;amp;height=720&amp;amp;face=726_156_858_300,https://scrap.kakaocdn.net/dn/lYe5o/hyYm6Gyn58/4zYXBlDYKAw9vLwE6Lcg71/img.png?width=1280&amp;amp;height=720&amp;amp;face=726_156_858_300,https://scrap.kakaocdn.net/dn/dx7Jf4/hyYjxeCzEI/Cjec3r4NTmi4WQXyQknDHK/img.png?width=840&amp;amp;height=996&amp;amp;face=339_208_530_416');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript Bundle Diet&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹사이트 기능이 많아짐에 따라 점점 커지는 JavaScript Bundle Size. 웹사이트 속도는 느려지고, 잠재 고객을 잃을 수도 있습니다. 라이브러리를 사용하는 그리고 제공하는 관점에서 Bundle Size가 커질&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;toss.im&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://naver.me/52RYun71&quot;&gt;https://naver.me/52RYun71&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1740816063488&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;플레이 네이버(PLAY NAVER)&quot; data-og-description=&quot;[팀네이버 컨퍼런스 DAN 24] 여러분의 웹서비스에는 꼭 필요한 것만 있나요? 번들사이즈 최소화를 통한 웹 성능 개선&quot; data-og-host=&quot;bridge-now.naver.com&quot; data-og-source-url=&quot;https://naver.me/52RYun71&quot; data-og-url=&quot;https://bridge-now.naver.com/bridge?target_url=https%3A%2F%2Ftv.naver.com%2Fv%2F67445777&amp;amp;scheme=naverplayer%3A%2F%2Fvod_play%3FclipNo%3D67445777%26masterVideoId%3D4BAAAB28BF2BDE043DF87F0CCCD89C1657BB%26serviceId%3D2010%26currentTime%3D0%26curVideoEncodingOptionId%3D720P_1280_2048_192_B%26watchedAdvertise%3Dfalse&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdTE5g/hyYmVSAFMP/Va25woZBdfycgKoJVotPe1/img.png?width=1109&amp;amp;height=624&amp;amp;face=0_0_1109_624,https://scrap.kakaocdn.net/dn/fJAsY/hyYmLh8nyB/BtPuIu9Bwu8X5KbQmcDWI1/img.png?width=1109&amp;amp;height=624&amp;amp;face=0_0_1109_624&quot;&gt;&lt;a href=&quot;https://naver.me/52RYun71&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://naver.me/52RYun71&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdTE5g/hyYmVSAFMP/Va25woZBdfycgKoJVotPe1/img.png?width=1109&amp;amp;height=624&amp;amp;face=0_0_1109_624,https://scrap.kakaocdn.net/dn/fJAsY/hyYmLh8nyB/BtPuIu9Bwu8X5KbQmcDWI1/img.png?width=1109&amp;amp;height=624&amp;amp;face=0_0_1109_624');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;플레이 네이버(PLAY NAVER)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[팀네이버 컨퍼런스 DAN 24] 여러분의 웹서비스에는 꼭 필요한 것만 있나요? 번들사이즈 최소화를 통한 웹 성능 개선&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bridge-now.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/606</guid>
      <comments>https://insengnewbie.tistory.com/606#entry606comment</comments>
      <pubDate>Sat, 1 Mar 2025 17:10:40 +0900</pubDate>
    </item>
    <item>
      <title>[FE] flex 팀의 마이크로 프론트엔드 전환기 - 정리</title>
      <link>https://insengnewbie.tistory.com/605</link>
      <description>&lt;h1&gt;기존&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;17개의 nextjs 앱의 모노레포, 인프라 레벨에서 라우팅해줌&lt;/li&gt;
&lt;li&gt;제품 전체 200개 이상의 url&lt;/li&gt;
&lt;li&gt;제품 전체 8.7GB 코드베이스&lt;/li&gt;
&lt;li&gt;각 스쿼드에서 페이지 별로 앱을 나눠서 개발&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 이동할 때마다 유저에게 하나의 앱을 쓰는 경험을 주기 힘듦.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 nextjs 앱이기 때문에 페이지 로딩 시 html, js, css를 전부 다시 내려받아야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스쿼드 독립적, 효율적인 개발이 힘듦.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 간의 의존성이 있음&lt;/li&gt;
&lt;li&gt;공통 컴포넌트의 경우 한번 수정되도 모든 앱에 영향을 끼쳐서 작은 이슈에도 전체 롤백해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Module Federation PoC&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nextjs에 적용 가능한 오픈소스 기반의 Module Federation을 적용할 수 있는 플러그인 존재&lt;/li&gt;
&lt;li&gt;기존 앱에 적용해보니까 동작하는 것은 확인&lt;/li&gt;
&lt;li&gt;그러나, Next.js 측에서 모듈 페더레이션을 공식적으로 지원할 생각이 없어보였고, 오픈 소스 기반의 플러그인에 큰 프로젝트의 아키텍처를 맡길 수 없다는 판단내림&lt;/li&gt;
&lt;li&gt;&amp;rArr; Nextjs 떼어내기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nextjs를 전면 SPA로 마이그레이션하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js의 SSR을 하드하게 사용하고 있지 않다.&lt;/li&gt;
&lt;li&gt;flex는 B2B saas라서 SEO 등 SSR이 강점을 보일 수 있는 부분이 적다.&lt;/li&gt;
&lt;li&gt;한정된 리소스로 Module Federation을 적용하면서 SSR까지 가져가기는 힘드라.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성과&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유저 경험 개선 : 하나의 앱을 쓰는 경험&lt;/li&gt;
&lt;li&gt;배포 단위 세분화 : 전체 장애/롤백 상황 최소화&lt;/li&gt;
&lt;li&gt;개발 생산성 개선 : 빌드 시간 평균 70% 개선&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;남은 문제점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개발자 경험(DX) : nextjs가 주던 개발 편의성을 누리지 못하게됨&lt;/li&gt;
&lt;li&gt;아직 존재하는 런타임 리스크 : 앱 간 상태 분리, 배포 순서 등&lt;/li&gt;
&lt;li&gt;SSR 구현 및 서버 리소스 활용 : 정적 자원 서빙 역할만 하고 있음&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/605</guid>
      <comments>https://insengnewbie.tistory.com/605#entry605comment</comments>
      <pubDate>Sat, 1 Mar 2025 16:42:51 +0900</pubDate>
    </item>
    <item>
      <title>[FE] 섬세한 ISFP의 코드 가독성 개선 경험 - 정리</title>
      <link>https://insengnewbie.tistory.com/604</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/emGLxi0LvNI?si=NXWu45SAjQAdLAmn&quot;&gt;https://youtu.be/emGLxi0LvNI?si=NXWu45SAjQAdLAmn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;섬세한 ISFP의 코드 가독성 개선 경험 / if(kakao)2022&lt;/li&gt;
&lt;li&gt;발표자가 집중한 부분은 &quot;어떻게 하면 정확한 단어를 고를지&quot;와 &quot;어떻게 하면 한 눈에 잘 보이는 형태로 작성할 수 있을지&quot;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정확한 단어 고르기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다른 뜻을 가진 단어와 구분하기&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;const data = await loadData();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;load : 가져와서 싣다&lt;/li&gt;
&lt;li&gt;fetch : 가져오다&lt;/li&gt;
&lt;li&gt;fetchData로 함수명을 바꾸던가 &lt;code&gt;const success = await loadData()&lt;/code&gt; 로 바꾸는게 적절해보인다고 함. 성공여부만 반환하도록&lt;/li&gt;
&lt;li&gt;리액트쿼리에서 isLoading, isFetching의 의미를 구분하는데 도움이 됨.&lt;/li&gt;
&lt;li&gt;get : 가져오다&lt;/li&gt;
&lt;li&gt;query : 질문하다&lt;/li&gt;
&lt;li&gt;테스팅 라이브러리에서 제공하는 getByText, queryByText는 다른 동작을 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getByText : 결과값이 없으면 에러&lt;/li&gt;
&lt;li&gt;queryByText : 결과값이 없으면 null 반환&lt;/li&gt;
&lt;li&gt;단어의 뉘앙스 차이에 따라 함수 동작이 달라짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;미묘한 뉘앙스 차이를 이해하여 코드 문맥을 이해하는 데 도움이 됨&lt;/li&gt;
&lt;li&gt;UI 컴포넌트의 정확한 명칭을 이해하는 것도 도움이 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카드와 박스&lt;/li&gt;
&lt;li&gt;앱바와 글로벌 내비바, 로컬 내비바&lt;/li&gt;
&lt;li&gt;카드 : 하나의 주제로 묶인 콘텐츠와 액션&lt;/li&gt;
&lt;li&gt;박스 : 내용물을 감싸는 래퍼&lt;/li&gt;
&lt;li&gt;select : 하나의 결과를 항상 선택한다.&lt;/li&gt;
&lt;li&gt;search : 모든 결과를 찾는 것에 그친다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보다 구체적인 단어로 바꾸기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;짜증이라는 단어에는 여러 감정이 뭉뚱그려져 있다. 보다 구체적이게 표현할 수 있는 방법이 많다.
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (expirationTime &amp;lt; PROMOTION_END_TIME) {
  return remainTime / totalTime;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (expirationDate &amp;lt; PROMOTION_END_DATE) {
    return remainDuration / totalDuration;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간(time) =&amp;gt; 시각(date)&lt;/li&gt;
&lt;li&gt;시간 =&amp;gt; 기간(duration)&lt;/li&gt;
&lt;li&gt;단어로 값의 의미를 알 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정확하지 않아도 좋은 경우&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const MIN_TO_SEC = 60;
const HOUR_TO_SEC = MIN_TO_SEC * 60;
const DAY_TO_SEC = HOUR_TO_SEC * 24;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const MIN = 60;
const HOUR = MIN * 60;
const DAY = HOUR * 24;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어가 의미를 정확하게 전달하지는 않게 됐지만, 간결해지고 맥락을 파악하기 쉬워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잘 보이는 형태로 작성해보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책은 표, 각주, 용어정리, 목차 등을 활용해서 알기 쉽게 설명한다.&lt;/li&gt;
&lt;li&gt;삼항연사자로 떡칠된 조건문을 표처럼 볼 수 있도록 개선함.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;switch (type) {
    case &quot;apple&quot;:
        str = 'a';
        break;
    case &quot;banana&quot;;
        str = 'b';
        break;
    default:
        str = 'c';
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;const FRUIT_MAP = {
    apple: 'a',
    banana: 'b',
    DEFAULT: 'c'
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;switch 문도 직관적이긴 하지만, 조금 더 시각적으로 한 눈에 이해할 수 있도록 map을 활용&lt;/li&gt;
&lt;li&gt;목차 모델을 사용해 zindex 꼬임 문제를 해결&lt;/li&gt;
&lt;li&gt;모든 zindex를 한 곳에서 관리하도록해서 한 눈에 찾기 쉬워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용어 정리&lt;/li&gt;
&lt;li&gt;다른 개발자가 의도를 이해하기 쉽도록 변수명에 의도를 드러냄&lt;/li&gt;
&lt;li&gt;코드는 길어졌지만, 의도를 드러냄으로써 가독성이 좋아짐&lt;/li&gt;
&lt;li&gt;각주 : 원하는 핵심만 전달하고 부연설명이 필요한 경우 윗첨자로 정보 전달&lt;/li&gt;
&lt;li&gt;이벤트 로깅 클릭 이벤트를 모아서 처리하는 HOC 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 후기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유용하다&lt;/li&gt;
&lt;li&gt;리팩토링 책에서 공부했던 내용을 실제 사례로 보면서 이해하기 쉬웠다.&lt;/li&gt;
&lt;li&gt;미묘한 뉘앙스를 더 잘 이해할 수 있도록 영어 공부의 필요성을 느꼈다.&lt;/li&gt;
&lt;li&gt;특히 마지막의 각주 모델로 로깅을 추상화하는 과정이 흥미롭다.1&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Front-End</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/604</guid>
      <comments>https://insengnewbie.tistory.com/604#entry604comment</comments>
      <pubDate>Mon, 24 Feb 2025 21:57:32 +0900</pubDate>
    </item>
    <item>
      <title>디자인 리드님이 그려주심</title>
      <link>https://insengnewbie.tistory.com/603</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oeyB4/btsMlWKq77d/6bFKWVOGbdE2JZYTjeJo91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oeyB4/btsMlWKq77d/6bFKWVOGbdE2JZYTjeJo91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oeyB4/btsMlWKq77d/6bFKWVOGbdE2JZYTjeJo91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoeyB4%2FbtsMlWKq77d%2F6bFKWVOGbdE2JZYTjeJo91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;399&quot; height=&quot;532&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감성 굿&lt;/p&gt;</description>
      <category>혼잣말</category>
      <author>KimCookieYa</author>
      <guid isPermaLink="true">https://insengnewbie.tistory.com/603</guid>
      <comments>https://insengnewbie.tistory.com/603#entry603comment</comments>
      <pubDate>Tue, 18 Feb 2025 00:49:50 +0900</pubDate>
    </item>
  </channel>
</rss>