<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 오승연</title>
    <description>The latest articles on DEV Community by 오승연 (@seungyeon_).</description>
    <link>https://dev.to/seungyeon_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3608254%2Fd8f68d4d-0262-4523-bbd9-d2c38253e505.jpg</url>
      <title>DEV Community: 오승연</title>
      <link>https://dev.to/seungyeon_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seungyeon_"/>
    <language>en</language>
    <item>
      <title>교육 산업을 읽는 법: 신문 스크랩을 시작하며</title>
      <dc:creator>오승연</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:35:12 +0000</pubDate>
      <link>https://dev.to/seungyeon_/gyoyug-saneobeul-ilgneun-beob-sinmun-seukeuraebeul-sijaghamyeo-188g</link>
      <guid>https://dev.to/seungyeon_/gyoyug-saneobeul-ilgneun-beob-sinmun-seukeuraebeul-sijaghamyeo-188g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;2025년 12월 21에 작성한 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;취업 준비 중 지원동기를 고민하다가 우연히 경제 신문 스크랩에 관한 영상을 보게 됐다. (영상 링크는 아래에 있다.)&lt;/p&gt;

&lt;p&gt;처음엔 지원동기 작성에 도움이 될까 싶어 본 영상이었는데, 보다 보니 '신문 스크랩으로 산업을 분석할 수 있다'는 지점이 눈에 들어왔다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://o5sy.notion.site/2d1d9c31c757803c92a6d8950a2cba52?v=2d1d9c31c7578049804c000c549dae30&amp;amp;source=copy_link" rel="noopener noreferrer"&gt;스크랩 아카이브 링크&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  왜 필요한가
&lt;/h2&gt;

&lt;p&gt;나는 도메인을 제대로 이해해야 더 좋은 해결책을 제시할 수 있다고 믿어왔다. 문제는 방법이었다. 도메인을 이해하려면 그 산업에 직접 몸담아 경험을 쌓거나 현직자 인터뷰를 해야 가능하다고 생각했다.&lt;/p&gt;

&lt;p&gt;그런데 신문 스크랩이라는 방법을 알게 되면서 생각이 바뀌었다. 꼭 현장에 있지 않아도, 신문을 통해 트렌드를 읽고 산업의 흐름을 파악할 수 있겠다는 가능성이 보였다.&lt;/p&gt;

&lt;h2&gt;
  
  
  어떻게 할 것인가
&lt;/h2&gt;

&lt;p&gt;영상에서 소개하는 스크랩 방법은 이렇다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;경제신문 A면을 전부 훑어보며 빠르게 스크랩할 기사를 본다&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&gt;나는 좀 더 분명한 목적이 있었기에, 이에 맞게 스크랩 방법 또한 구체화했다.&lt;/p&gt;

&lt;p&gt;(1) 산업을 파악하고 교육에 대한 관심을 유지하는 것&lt;br&gt;
(2) 멘토링에 적용할 방법을 찾는 것&lt;/p&gt;

&lt;h3&gt;
  
  
  스크랩 방법 및 계획
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. 빠르게 스크랩할 기사를 체크한다&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;신문사는 &lt;a href="https://www.hangyo.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;한국교육신문&lt;/strong&gt;&lt;/a&gt;으로 정했다.&lt;/p&gt;

&lt;p&gt;1961년 창간 이래 교육 이슈와 교육 콘텐츠를 꾸준히 발행해온 곳으로, 매주 월요일 발행되는 &lt;a href="https://www.hangyo.com/news/pdf.html" rel="noopener noreferrer"&gt;주간지&lt;/a&gt;를 중심으로 살펴볼 예정이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;주로 찾아볼 기사는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;성인 취업 교육과 에듀테크 산업 관련 기사&lt;/li&gt;
&lt;li&gt;효과적인 교육 방법을 다룬 기사&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;다만 공교육 중심의 내용이 많아서, 내가 원하는 내용의 기사가 없는 주도 있어 보인다. 그럴 땐 그 주에 발행된 기사를 훑어보면서 교육 전반의 흐름이라도 파악하려고 한다.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. 선정한 기사를 스크랩 양식에 옮긴다&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;스크랩 양식

&lt;ul&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;li&gt;IT 서비스를 통한 사업 기회 생각해보기 (토이 프로젝트 아이디어 발굴)&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;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3. 숫자는 빨간색, 기술이나 트렌드는 파란색으로 표시한다&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;하이라이팅 방법은 빠르게 본문을 훑어보기에 좋을 것 같아서 그대로 적용한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  앞으로는
&lt;/h2&gt;

&lt;p&gt;실제로 해보면서 인사이트가 생기는 대로 방향을 조절해 나갈 생각이다. 완벽한 계획보다는 일단 시작하고 꾸준히 하는 게 중요하다!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?si=gndyDr1onnDzqpxR&amp;amp;t=291&amp;amp;v=oCCooQ9IMTo&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;경제 신문 스크랩 영상 - 면접왕 이형&lt;/a&gt;&lt;/p&gt;

</description>
      <category>education</category>
    </item>
    <item>
      <title>[JavaScript] 데이터 복사 이해하기 (feat. 얕은 복사, 깊은 복사)</title>
      <dc:creator>오승연</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:26:44 +0000</pubDate>
      <link>https://dev.to/seungyeon_/javascript-deiteo-bogsa-ihaehagi-feat-yateun-bogsa-gipeun-bogsa-243e</link>
      <guid>https://dev.to/seungyeon_/javascript-deiteo-bogsa-ihaehagi-feat-yateun-bogsa-gipeun-bogsa-243e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;2025년 12월 1일에 작성된 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;객체를 복사했는데 원본까지 바뀌는 경험 있으신가요? JavaScript에서는 데이터를 복사할 때 타입에 따라 동작 방식이 다르기 때문에 이런 일이 발생합니다. 이 글에서는 복사의 기본 원리부터 실전 복사 전략까지 단계별로 알아보겠습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  이 글을 통해 얻을 수 있는 것
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;데이터 타입에 따른 복사 방식의 차이를 이해한다&lt;/li&gt;
&lt;li&gt;객체 복사 시 주의해야 할 점을 안다&lt;/li&gt;
&lt;li&gt;상황에 맞는 복사 방법을 선택할 수 있다&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. JavaScript 데이터의 복사: 타입에 따라 다르다
&lt;/h2&gt;

&lt;p&gt;JavaScript의 타입은 크게 원시 값과 객체로 나뉩니다. 또한 변수에 값을 할당하거나 복사할 때, 값의 타입이 무엇인지에 따라 동작이 다릅니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript의 타입을 잘 모르겠다면 &lt;a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Data_structures" rel="noopener noreferrer"&gt;MDN 자료&lt;/a&gt;를 참고해보세요.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  원시 값 (Primitive Values)
&lt;/h3&gt;

&lt;p&gt;원시 값은 변수에 &lt;strong&gt;‘값 자체’&lt;/strong&gt;가 저장됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;종류&lt;/strong&gt;: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;bigint&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt;, &lt;code&gt;symbol&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt; 가 있습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;복사 시&lt;/strong&gt; 완전히 독립된 새로운 값이 생성됩니다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// a의 값(100)이 복사되어 b에 저장&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// b만 변경됨&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 100&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 50 (서로 독립적임)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2m7y17a5gq4wtb5066po.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2m7y17a5gq4wtb5066po.png" alt=" " width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  객체 (Object)
&lt;/h3&gt;

&lt;p&gt;객체는 변수에 값이 아닌 &lt;strong&gt;‘메모리 주소(참조)’&lt;/strong&gt;가 저장됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;종류&lt;/strong&gt;: &lt;code&gt;object&lt;/code&gt;, &lt;code&gt;array&lt;/code&gt;가 있으며, &lt;code&gt;function&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt; 또한 객체의 일종입니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;복사 시&lt;/strong&gt; 객체를 가리키는 주소가 복사됩니다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;myArr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;copyArr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myArr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 배열을 가리키는 주소가 복사됨&lt;/span&gt;

&lt;span class="nx"&gt;copyArr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 복사본을 수정&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;copyArr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ["hello"]&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myArr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ["hello"]  (원본도 변경됨)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0f65h3ca3at5tx0plsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0f65h3ca3at5tx0plsv.png" alt=" " width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 객체 복사 시 주의점: 참조 공유 문제
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;const a = b;&lt;/code&gt;와 같이 단순 대입으로 객체를 복사하면 참조만 복사되므로, 원본과 복사본이 공유됩니다. &lt;/p&gt;

&lt;p&gt;이를 피하려면 새로운 객체를 생성해야 합니다.&lt;/p&gt;

&lt;p&gt;동일한 프로퍼티를 가지면서 새로운 객체를 생성하는 방법으로는 &lt;a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign" rel="noopener noreferrer"&gt;Object.assign()&lt;/a&gt;이나 배열의 경우 &lt;a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/slice" rel="noopener noreferrer"&gt;Array.prototype.slice()&lt;/a&gt; 등이 있지만, 글에선 &lt;a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax#%EA%B0%9D%EC%B2%B4_%EB%A6%AC%ED%84%B0%EB%9F%B4%EC%97%90%EC%84%9C%EC%9D%98_%EC%A0%84%EA%B0%9C" rel="noopener noreferrer"&gt;전개 연산자&lt;/a&gt;를 사용해보겠습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  전개 연산자로 새 객체 만들기
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// 새로운 객체 생성&lt;/span&gt;

&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 30 (원본 영향 없음)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 31&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;원본 객체의 속성들을 새로운 객체에 복사했고, 각 속성의 값은 원시 값이므로 독립적으로 수정할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  중첩 객체를 가진 경우
&lt;/h3&gt;

&lt;p&gt;이번에는 속성의 값으로 객체가 있는 경우를 같은 방식으로 복사해보겠습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Seoul&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// 중첩 객체&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Busan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Busan' (원본도 변경됨)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;중첩 객체는 참조가 복사되므로 원본이 변경되었습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user → { name: 'Lee', address: → [0x001] }
copy → { name: 'Kim', address: → [0x001] }  (같은 주소 공유)
                                    ↓
                            { city: 'Busan' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;왜 이런 일이 일어났을까요?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;데이터 복사는 값의 타입에 따라 달라진다는 것, 기억하시나요? &lt;/p&gt;

&lt;p&gt;전개 연산자를 통해 원본 객체의 속성을 복사하는 과정에서 원시 값은 ‘값 자체’를 복사했지만, 중첩 객체는 ‘메모리 주소(참조)’를 복사하면서 속성 값 변경 시 서로 영향을 미치게 되는 것입니다.&lt;/p&gt;

&lt;p&gt;이처럼 최상위 속성(= 1단계 깊이의 속성)만 복사하는 것을 복사 깊이가 얕다는 의미로, &lt;strong&gt;"얕은 복사(Shallow Copy)"&lt;/strong&gt;라고 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 중첩 객체를 안전하게 복사하는 방법
&lt;/h2&gt;

&lt;p&gt;얕은 복사로 인한 문제를 해결하고, 원본을 보호하면서 복사하는 방법은 크게 두 가지가 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3-1. &lt;strong&gt;전개 연산자를 중첩해 필요한 부분만 새로 만들기&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;변경하려는 속성의 경로를 따라 전개 연산자를 중첩해서 사용하면, 필요한 부분만 새로 만들 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Seoul&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12345&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// address.city만 변경하고 싶을 때&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// user의 최상위 속성들 복사&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;            &lt;span class="c1"&gt;// address를 새 객체로 덮어쓰기&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 기존 address 속성들 복사 (zip 유지)&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Busan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;       &lt;span class="c1"&gt;// city만 변경&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// 'Seoul' (원본 유지)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Busan'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;배열 내부 객체를 수정할 때는 &lt;a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map" rel="noopener noreferrer"&gt;Array.prototype.map()&lt;/a&gt;을 활용할 수도 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// id가 1인 사용자의 active만 변경&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이처럼 전개 구문을 중첩해서 사용하면 필요한 부분만 새로 만들어 복사할 수 있습니다. 하지만 이 방법은 &lt;strong&gt;어떤 속성을 변경할지 미리 알고 있을 때&lt;/strong&gt; 유용합니다. &lt;/p&gt;

&lt;h3&gt;
  
  
  3-2. 모든 속성을 독립적으로 복사하기: 깊은 복사
&lt;/h3&gt;

&lt;p&gt;사용자가 자유롭게 데이터를 바꾸는 환경에서는, 편집 시작 시점의 원본 데이터를 통째로 보관해두는 것이 효율적입니다.&lt;/p&gt;

&lt;p&gt;또한 히스토리를 유지하거나 로그를 남겨야 하는 경우에도 각 시점의 상태를 독립적으로 저장해야 정확한 기록을 유지할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;예시 시나리오: 텍스트 에디터의 취소 기능&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;사용자가 문서를 편집하다가 "실행 취소(ctrl+z)"를 하면 이전 상태로 되돌아가야 합니다. 사용자가 어떤 부분을 수정할지 예측할 수 없으므로, 편집 전 문서 상태를 통째로 저장해두어야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 현재 문서 상태&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentDocumentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;formatting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// 얕은 복사로는 안전하지 않음&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// formatting은 여전히 공유됨&lt;/span&gt;

&lt;span class="c1"&gt;// 사용자가 여러 작업 수행&lt;/span&gt;
&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello JavaScript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 실행 취소 시도&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// formatting은 이미 변경되어 복구할 수 없는 문제 발생&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이런 경우 &lt;strong&gt;모든 중첩된 객체를 완전히 새로운 메모리 공간에 복사&lt;/strong&gt;해야 합니다. 이를 &lt;strong&gt;"깊은 복사(Deep Copy)"&lt;/strong&gt;라고 합니다.&lt;/p&gt;

&lt;p&gt;그 중 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone" rel="noopener noreferrer"&gt;structuredClone()&lt;/a&gt;은 최신 브라우저에서 지원하는 표준 API로, &lt;a href="https://developer.mozilla.org/ko/docs/Web/API/Web_Workers_API/Structured_clone_algorithm" rel="noopener noreferrer"&gt;structured clone 알고리즘&lt;/a&gt;을 사용하여 깊은 복사를 수행합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 편집 전 깊은 복사로 스냅샷 저장&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;structuredClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 사용자가 수정&lt;/span&gt;
&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 실행 취소 시 안전하게 복원&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDocumentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 14 (원본 상태 유지)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;깊은 복사 방법 비교표&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;방법&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;structuredClone()&lt;/td&gt;
&lt;td&gt;빠르고 표준&lt;/td&gt;
&lt;td&gt;호환성 확인 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_.cloneDeep()&lt;/td&gt;
&lt;td&gt;모든 타입 복사 가능&lt;/td&gt;
&lt;td&gt;번들 사이즈 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Immer&lt;/td&gt;
&lt;td&gt;불변 업데이트 간결&lt;/td&gt;
&lt;td&gt;러닝커브 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON.parse(JSON.stringify())&lt;/td&gt;
&lt;td&gt;라이브러리 없이 간단 사용&lt;/td&gt;
&lt;td&gt;Date, function, undefined 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  요약
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;타입에 따라 복사 방식이 다름&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;원시 값: 값 자체 복사 → 독립적&lt;/li&gt;
&lt;li&gt;객체: 참조 복사 → 공유됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;객체 복사의 기본은 "얕은” 복사 방식&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;전개 연산자(&lt;code&gt;{...obj}&lt;/code&gt;, &lt;code&gt;[...arr]&lt;/code&gt;)는 1단계만 새로 만듦&lt;/li&gt;
&lt;li&gt;필요한 경로까지 중첩 전개로 해결 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;필요시 "깊은" 복사 방식 적용&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;중첩 객체 + 원본 보호 필요 시 &lt;code&gt;structuredClone()&lt;/code&gt; 등 API 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  퀴즈
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;다음 코드의 결과를 예측해보세요.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;다음 코드의 결과를 예측해보세요.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  참고 자료
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/ko/docs/Glossary/Shallow_copy" rel="noopener noreferrer"&gt;MDN - Shallow copy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/ko/docs/Glossary/Deep_copy" rel="noopener noreferrer"&gt;MDN - Deep copy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ko.javascript.info/object-copy" rel="noopener noreferrer"&gt;https://ko.javascript.info/object-copy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>개발자를 위한 AI 활용 학습법: 4단계 학습 방법론</title>
      <dc:creator>오승연</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:22:33 +0000</pubDate>
      <link>https://dev.to/seungyeon_/gaebaljareul-wihan-ai-hwalyong-hagseubbeob-4dangye-hagseub-bangbeobron-1ldf</link>
      <guid>https://dev.to/seungyeon_/gaebaljareul-wihan-ai-hwalyong-hagseubbeob-4dangye-hagseub-bangbeobron-1ldf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;2025년 8월 12일에 작성된 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;개발 학습자들이 자주 하는 질문이 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“강의는 다 들었는데, 실제 코드에 어떻게 적용할지 모르겠어요.”&lt;/em&gt;&lt;br&gt;
&lt;em&gt;“궁금한 게 있으면 질문하라는데, 무엇을 물어봐야 할지 모르겠어요.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;이론으로만 이해했다고 생각하고 넘어가면서 실제 적용에서 막히게 되는 것입니다.&lt;/p&gt;

&lt;p&gt;이 글에서는 AI를 활용해 능동적이고 체계적으로 기술을 학습할 수 있는 4단계 학습법을 소개합니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  기존 학습법의 문제점
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. 수동적인 단방향 학습
&lt;/h3&gt;

&lt;p&gt;대부분의 학습 자료는 단방향으로 정보를 전달합니다. &lt;br&gt;
간단한 실습을 진행해도 능동적으로 사용하지 않으면 기억에 오래 남지 않습니다.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. 무한 루프에 빠진 학습
&lt;/h3&gt;

&lt;p&gt;모르는 내용을 발견하면 → 새로운 자료 찾기 → 또 다른 모르는 내용 발견 → 무한 반복&lt;br&gt;
결국 어떤 것도 제대로 마무리하지 못한 채 의욕만 떨어지게 됩니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  AI로 달라진 학습 환경
&lt;/h2&gt;

&lt;p&gt;AI의 등장으로 효과적인 학습이 가능해졌습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;즉시성&lt;/strong&gt;: 개념 학습 직후 실습 환경과 예제 생성&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;상호작용&lt;/strong&gt;: 실시간 질문과 피드백으로 능동적 학습&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;개인화&lt;/strong&gt;: 내 수준과 목적에 맞춘 맞춤형 컨텐츠 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4단계 학습 방법론
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1단계: 개념 파악
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;대상&lt;/strong&gt;: 새로운 기술을 처음 학습하려는 학습자&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;목표&lt;/strong&gt;: 기술의 개념을 빠르게 파악하기&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;방법&lt;/strong&gt;: 기초 강의 수강, 공식 문서의 개요 섹션 읽기 + AI로 모호한 부분 즉시 해결&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;핵심은 깊이보다는 &lt;strong&gt;전체 흐름을 이해하는 것&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;p&gt;처음 기술을 접하기 때문에 다양한 자료를 보며 용어와 개념에 익숙해져야 합니다.&lt;br&gt;
정의, 핵심 기능, 필요성(등장 배경), 기본 동작 원리를 1차적으로 설명할 수 있는 수준으로 학습하면 충분합니다.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;AI 활용 예시&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 용어 설명 요청
React에서 말하는 '상태'가 정확히 뭐야? 쉽게 설명해줘

// 문서 이해 보조
이 공식 문서 설명이 이해가 안 돼. 다른 방식으로 설명해줘
[문서 내용 붙여넣기]

// 큰 그림 정리
지금까지 [학습한 내용들]을 학습했어
[기술명]의 핵심 개념들을 하나의 흐름으로 연결해서 설명해줘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2단계: 기본 실습
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;대상&lt;/strong&gt;: 개념은 이해했지만 실제 적용이 어려운 학습자&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;목표&lt;/strong&gt;: 문법, 기본 API, 대표적 사용 패턴 익히기&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;방법&lt;/strong&gt;: AI 맞춤형 실습 진행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;핵심은 사용법을 익히며 코드를 치는데 익숙해지는 것입니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;실습 진행 순서&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&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;피드백을 바탕으로 개선해봅니다.&lt;/li&gt;
&lt;li&gt;다음 조건을 만족할 때까지 반복합니다.

&lt;ul&gt;
&lt;li&gt;예제 없이도 기본 구조를 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;사용법을 다른 사람에게 설명할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 &lt;strong&gt;AI 활용 프롬프트&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;실습 생성 요청&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[방금 배운 개념]을 바로 적용할 수 있는 실습을 만들어줘
목표는 문법, 기본 API, 대표적 사용 패턴을 익히는 거야
- 5분 소요 실습 1개 (기본 사용법)
- 15분 소요 실습 1개 (응용 사례)
- 각각 단계별 가이드와 예상 결과물 포함
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;코드리뷰 요청&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;내가 작성한 코드를 아래 형식에 맞춰 리뷰해줘
- 실습 목표: 문법과 기본 API, 대표적 사용 패턴을 익히고, 예제 없이도 구현 가능하게 되는 것
- 기본 문법, API 사용이 적절한지 평가
- 대표적 사용 패턴과 비교해 부족한 점 2가지 제시
- 이해를 돕기 위한 개선 예시 코드 작성
- 같은 기능을 더 효율적으로 구현할 수 있는 방법 제안
- 다음 단계로 공부/실습하면 좋을 주제 1개 추천

[코드 첨부]
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3단계: 심화 탐구
&lt;/h3&gt;

&lt;p&gt;기본 개념을 익힌 후, 목적에 따라 더 깊이 이해하는 단계입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) 심화 개념 학습
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;대상&lt;/strong&gt;: 기본 개념은 이해했지만 내부 동작 원리가 궁금한 학습자&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;목표&lt;/strong&gt;: 기술의 동작 원리 등의 개념을 깊이 있게 이해&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;방법&lt;/strong&gt;: AI와 소크라테스식 문답 진행&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💡 AI 활용 프롬프트&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;나는 지금까지 [기술명]의 [학습 내용]을 학습했어
소크라테스 문답법으로 내 이해도를 확인하고 심화 개념 학습을 도와줘
시작할 수 있게 먼저 질문을 던져줘

- 기본 개념 확인부터 시작
- 내부 동작 원리와 '왜 그렇게 설계됐는지' 중심으로 질문
- 틀리면 힌트만, 맞으면 더 깊은 질문
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) 실제 프로젝트 적용
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;대상&lt;/strong&gt;: 학습한 내용을 실제 프로젝트에 써보고 싶은 학습자&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;목표&lt;/strong&gt;: 프로젝트 내 적용 가능한 부분을 찾아 상황에 맞게 적용&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;방법&lt;/strong&gt;: AI와 적용 전략 수립 및 구현&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💡 AI 활용 프롬프트&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;학습한 [학습 내용]을 [프로젝트 상황]에 적용하려고 해
실무 관점에서 프로젝트 내에 적용 가능한 부분을 찾아, 상황에 맞게 적용할 수 있게 도와줘

- 언제, 어떻게 사용하는 게 적절한지 판단 기준
- 실제 코드 예시와 패턴
- 흔히 발생하는 실수와 주의할 점
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4단계: 학습 마무리
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;대상&lt;/strong&gt;: 학습한 내용을 정리하고 마무리하고 싶은 학습자&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;목표&lt;/strong&gt;: 진짜 이해했는지 최종 점검&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;방법&lt;/strong&gt;: 설명하기, 모의면접, 글 작성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI에게 설명해보면서 막히는 부분을 체크하고, 더 궁금한 내용을 정리해봅니다.&lt;br&gt;
(설명은 음성인식으로 타이핑되는 기능을 활용해보세요.)&lt;/p&gt;

&lt;p&gt;시간에 따라 더 알아보거나 나중에 알아볼 내용으로 쌓아두고 마무리할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 AI 활용 프롬프트&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;개념 설명&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[기술명]을 이해했는지 확인하기 위해 설명해볼게
내 설명에서 부족한 부분이나 틀린 부분 지적해줘

[음성인식으로 설명]
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;모의 면접&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;너는 시니어 개발자이자 면접관이야
[기술명]에 대해 3개의 면접 질문을 해줘
- 기본 개념 이해도 확인 질문 1개
- 실무 적용 경험 1개
- 트러블슈팅/문제해결 1개
모든 답변이 끝나면 부족한 부분과 추가 학습 포인트 알려줘
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;기존 학습법의 문제점에서 출발해, 새로운 기술을 체계적으로 학습하는 방법을 정리해보았습니다.&lt;/p&gt;

&lt;p&gt;AI는 훌륭한 학습 도구이지만, 중요한 것은 능동적으로 활용하는 학습자의 태도입니다.&lt;br&gt;
기술 학습의 목적이 '문제 해결'이라는 것을 잊지 말고, AI가 제공하는 정보를 비판적인 태도로 검증하는 절차 또한 필요합니다.&lt;/p&gt;

&lt;p&gt;이 방법론이 기술 학습 시 이론과 실전 사이 간극을 메우는 데 도움이 되기를 희망합니다!&lt;/p&gt;

</description>
      <category>education</category>
      <category>promptengineering</category>
      <category>ai</category>
    </item>
    <item>
      <title>바이브코딩으로 10일 만에 완성한 자동화 도구 개발기</title>
      <dc:creator>오승연</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:19:39 +0000</pubDate>
      <link>https://dev.to/seungyeon_/baibeukodingeuro-10il-mane-wanseonghan-jadonghwa-dogu-gaebalgi-255o</link>
      <guid>https://dev.to/seungyeon_/baibeukodingeuro-10il-mane-wanseonghan-jadonghwa-dogu-gaebalgi-255o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;2025년 7월 18일에 작성된 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2ywmmstu3paeuk4l927.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2ywmmstu3paeuk4l927.gif" alt="피드백 생성 동작" width="760" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. 바이브코딩으로 개발을 결심하기까지&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;반복되는 업무, 반복되는 고통&lt;/strong&gt;
&lt;/h3&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;h3&gt;
  
  
  &lt;strong&gt;자동화 가능한 지점 발견&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;이 과정에는 두 가지 패턴이 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 답변 정제 작업&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;오타 수정과 문장 완성&lt;/li&gt;
&lt;li&gt;일관된 문체로 수정&lt;/li&gt;
&lt;li&gt;LLM을 통해 수정: 메모한 답변 복사+붙여넣기 → 수정 요청 → 정제된 답변 복사+붙여넣기&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. 피드백 작성 작업&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;기술 내용에 대해 잘 파악한 점과 틀린 점 작성&lt;/li&gt;
&lt;li&gt;소프트스킬(답변 형식과 태도)에 대해 좋은 점과 아쉬운 점 작성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;둘 다 형식적이고 반복적인 작업이다. 이미 LLM을 활용하고 있었지만 매번 동일한 프롬프트를 요청했고, 복붙 과정까지도 자동화하면 더 시간을 단축할 수 있겠다는 생각이 들었다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;바이브코딩에 대한 관심&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;얼마 전 바이브코딩 강의를 수강했다. (커서 IDE 유료 결제도 했으니 뽕을 뽑아야 했다. 😃)&lt;/p&gt;

&lt;p&gt;강의는 준비된 프롬프트로 간단한 프로젝트를 개발하는 실습 위주로 진행됐다. 하지만 툴 사용이 익숙하지 않고 개발 프로젝트에 대한 이해도 부족해서 주도적으로 작업하기보다는 따라하며 익숙해지는 시간이었다. &lt;/p&gt;

&lt;p&gt;이번 프로젝트가 수강한 내용을 직접 적용해보며 AI 활용 방법을 체감하기에 적합해 보였다.&lt;/p&gt;

&lt;p&gt;참고로 내가 생각하는 바이브코딩은 AI에 의존해 기획과 개발을 하지만, 필요한 부분에서는 직접 코드 수정과 디버깅을 하면서 작업하는 것이다. (25.10.20 현시점 증강 코딩이라고 하더라.)&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. AI와 함께하는 기획부터 개발까지&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;문제 정의부터 다시 시작&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;작업의 첫 단계는 문제를 명확히 하는 것이었다. "자동화 툴을 만들려고 해. 문제 정의 단계를 하려는데 어떻게 시작하면 좋을까?"라고 하니, AI가 구체적인 질문들을 던져왔다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;현재 가장 반복적으로 하고 있는 작업들이 무엇인가요?&lt;/li&gt;
&lt;li&gt;하루 중 어떤 작업에 가장 많은 시간을 쓰고 계신가요?&lt;/li&gt;
&lt;li&gt;어떤 작업이 가장 번거롭고 시간이 오래 걸리나요?&lt;/li&gt;
&lt;li&gt;완전 자동화 vs 반자동화 중 어느 방향을 선호하시나요?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이런 질문들에 답하면서 요구사항이 구체화되었다. 두루뭉술했던 아이디어가 명확한 기능 명세로 바뀌었다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;기술 스택 선택&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;어떤 형태의 애플리케이션으로 개발할지 고민하면서 AI에게 후보를 추천해달라고 했다. &lt;/p&gt;

&lt;p&gt;내 개발 경험과 요구사항을 고려해서 세 가지 옵션을 제시했다. 각각의 장단점과 적합도까지 정리해줘서 따로 조사하지 않고 여러가지를 고려해볼 수 있었다.&lt;/p&gt;

&lt;p&gt;최종적으로는 크롬 확장 프로그램으로 결정했다. 리포트 작성 툴이 구글 스프레드 시트라 별도 웹앱으로 개발하지 않고 헬퍼 도구로 사용하고자 했다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chrome Extensions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;번들러

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vite (+ HMR을 위해 crxjs)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;라이브러리

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Sheets API&lt;/strong&gt;: 구글 시트에서 데이터를 읽고 쓰는 용도&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini API&lt;/strong&gt;: 텍스트 수정 작업 용도&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React, Tailwind CSS&lt;/strong&gt;: 확장 프로그램의 팝업 UI를 편리하게 개발하기 위한 용도&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vitest&lt;/strong&gt;: 테스팅 용도&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;개발 워크플로우의 변화&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;기존 개발 방식과 가장 큰 차이점은 룰 파일을 생성하는 것과 기술 학습 시점이었다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;이전 방식: 기획 → 필요한 기술 학습 → 개발 → 막히면 다시 학습&lt;/li&gt;
&lt;li&gt;바이브코딩 방식: 기획 → &lt;strong&gt;룰 파일 생성&lt;/strong&gt; → 개발 → &lt;strong&gt;필요한 부분 학습&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;바로 구현하기 전에 룰 파일을 생성하고 태스크를 정의했다. 각 태스크마다 테스트 코드를 먼저 작성하고, 구현하고, 검증하는 사이클을 반복했다.&lt;/p&gt;

&lt;p&gt;React, Tailwind CSS를 제외하곤 처음 다루는 기술이 많아 그때그때 필요한 부분만 찾아보며 진행했다. (이는 시행착오의 아픔이 되었다.)&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. 점진적 개선 과정&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;초기 MVP에서 겪은 시행착오&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;처음에는 AI가 제안하는 대로 계속 진행했다. (노룩 Run command ⌘⏎)&lt;/p&gt;

&lt;p&gt;TDD를 준수해 개발하도록 룰을 설정했기 때문에 테스트 코드는 통과하지만 실제로 구동해보니 동작하지 않았다. 내가 생각한 기능 범위를 벗어나 복잡한 구조로 작성하는 것도 많았다.&lt;/p&gt;

&lt;p&gt;여러 번 작업을 복구하면서 깨달았다. &lt;strong&gt;작업자인 인간이 통제권을 가지지 않으면 프로젝트가 산으로 간다.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;통제권 회복하기&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;이후 접근 방식을 바꿨다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;태스크를 더 작은 단위로 나누기&lt;/li&gt;
&lt;li&gt;각 태스크마다 동작 확인 필수&lt;/li&gt;
&lt;li&gt;MVP라는 점을 계속 강조하기&lt;/li&gt;
&lt;li&gt;복잡한 기능은 과감히 제거&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI에게 완전히 맡기지 않고 명확한 지시를 주고 결과를 검토하는 방식으로 변경했다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;리팩토링에서 배운 교훈&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;MVP로 빠르게 만든 코드는 당연히 거칠었다. 사용하지 않는 메서드들, 일관성 없는 구조, 불필요한 타입 정의들이 많았다.&lt;/p&gt;

&lt;p&gt;이미 AI에게 일임해 한차례 산으로 갔기 때문에 이번엔 직접 구조를 파악하며 수정하기로 했다. 그러기 위해서는 사용하는 기술의 방법을 알아야 했기에, 크롬 확장 프로그램 구조, 개발 가이드, 라이브러리 API 사용법들을 학습했다.&lt;/p&gt;

&lt;p&gt;작은 범위에 한해서는 AI를 활용했다. (커서 IDE 요청 수를 아끼려고 Gemini CLI를 썼다. 나쁘지 않다.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;사용하지 않는 코드 식별&lt;/li&gt;
&lt;li&gt;일관성 있는 구조로 수정할 때 코드 리뷰 요청&lt;/li&gt;
&lt;li&gt;폴더 구조 정리 및 import 경로 수정&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;단순 반복 작업은 AI가 정확하게 처리했다. 하지만 넓은 범위의 구조적 개선은 여전히 인간의 명확한 판단과 지시가 필요했다.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. 결과 및 소감&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;리포트 작성 시간 단축&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;기존: 1시간 20분 (답변 수정 20분 + 피드백 작성 1시간 이상)&lt;/li&gt;
&lt;li&gt;자동화 도구 사용 후: &lt;strong&gt;20~40분&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;답변 정제: 20초&lt;/li&gt;
&lt;li&gt;피드백 작성: 20~40분 (AI 피드백 생성 시간은 2분이지만 참고해서 작성하는 방향으로 진행)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;기존 1시간 20분에서 최소 20분으로 &lt;strong&gt;업무 시간이 절반 이상으로 단축&lt;/strong&gt;되었다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;AI가 잘하는 일 vs 못하는 일&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;바이브코딩 강의 중 강사님께서 이런 말씀을 하셨다. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“AI의 능력을 파악하세요. 바이브코딩을 반복하면서 AI가 잘하는 일, 못하는 일에 대한 경험치를 쌓으세요.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;직접 경험해보니, &lt;strong&gt;AI가 잘하는 일&lt;/strong&gt;은 다음과 같았다.&lt;br&gt;
&lt;strong&gt;추상적인 아이디어를 구체화&lt;/strong&gt;하는 것, &lt;strong&gt;핵심 요약과 정리&lt;/strong&gt;, TDD 등의 &lt;strong&gt;개발 원칙을 준수해 코드를 작성&lt;/strong&gt;하는 일, &lt;strong&gt;패턴이 명확한 반복 작업&lt;/strong&gt;, 여러 &lt;strong&gt;선택지를 비교&lt;/strong&gt;하고 그 이유를 &lt;strong&gt;논리적으로 설명&lt;/strong&gt;하는 일 등이 있었다.&lt;/p&gt;

&lt;p&gt;이런 점은 나처럼 완벽주의 성향이 있는 사람이 모호한 생각만 가지고도 일단 시작할 수 있게 만들어준다는 점에서 정말 큰 장점이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;반면, &lt;strong&gt;AI가 잘 못하는 일&lt;/strong&gt;은 다음과 같았다.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;복잡하고 큰 규모에서 일관성을 유지하며 처리&lt;/strong&gt;하는 일, &lt;strong&gt;실제 동작 환경에서의 디버깅&lt;/strong&gt;, &lt;strong&gt;프로젝트의 성격이나 사용자 관점을 고려한 개선&lt;/strong&gt;과 같은 것이다.&lt;/p&gt;

&lt;p&gt;사실 말로 설명할 수 있는 작업이라면 대부분 AI가 잘 해낼 거라고 생각한다. 그래서 ‘못하는 일’이라도 지시만 잘하면 상당 부분 해결할 수 있을 것이다. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결국 강사님 말씀처럼 AI와 함께 작업하며 경험치를 쌓아가고, 잘하고 못하는 일을 구분하면서 AI가 더 잘 일할 수 있도록 활용하는 방법을 익히는 게 중요하다는 걸 느꼈다.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;지속 가능한 코딩 에이전트 활용 방법&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;10일간의 경험을 통해 얻은 인사이트를 정리하며 글을 마무리하고자 한다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI의 모든 결과물은 반드시 리뷰할 것&lt;/strong&gt;&lt;br&gt;
줄 단위까지 세세하게 이해할 필요는 없지만, 전체 구조와 핵심 로직은 파악해야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;작업의 통제권은 작업자인 사람이 가질 것&lt;/strong&gt;&lt;br&gt;
태스크 단위로 진행 상황을 점검하고, 복잡해질 땐 단순화하는 판단이 필요하다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(새로운 기술을 사용할 경우) &lt;strong&gt;구조를 이해하고 실제 개발에 들어갈 것&lt;/strong&gt;&lt;br&gt;
실전에 쓰기 전에 간단한 실습을 통해 구조와 동작 방식을 먼저 익힌다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>vibecoding</category>
      <category>productivity</category>
      <category>education</category>
      <category>업무자동화</category>
    </item>
    <item>
      <title>CSR vs SSR 속도 비교 (feat. Web Vitals)</title>
      <dc:creator>오승연</dc:creator>
      <pubDate>Sun, 08 Mar 2026 09:13:58 +0000</pubDate>
      <link>https://dev.to/seungyeon_/csr-vs-ssr-sogdo-bigyo-feat-web-vitals-4aih</link>
      <guid>https://dev.to/seungyeon_/csr-vs-ssr-sogdo-bigyo-feat-web-vitals-4aih</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;CSR의 단점을 설명할 때 빠지지 않는 내용이 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"CSR은 초기 로딩 속도가 느리다."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그런데 좀 더 생각해보면 한가지 의문이 생깁니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"SSR도 서버에서 데이터를 fetch하면 그동안 화면이 비는 거 아닌가? 그러면 SSR이 CSR에 비해 느릴 수도 있지 있을까?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이렇게 헷갈리는 이유는 속도가 상대적인 개념이기 때문입니다. 무엇을 기준으로, 어느 시점부터 측정하느냐에 따라 설명이 달라질 수 있습니다.&lt;/p&gt;

&lt;p&gt;이 글에서는 CSR과 SSR의 속도를 비교해 명확히 설명할 수 있도록 TTFB, FCP, LCP 지표를 기준으로 알아봅니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  CSR과 SSR이란?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CSR (Client-Side Rendering)
&lt;/h3&gt;

&lt;p&gt;브라우저(클라이언트)에서 JavaScript를 실행해 화면을 그리는 방식입니다.&lt;/p&gt;

&lt;p&gt;서버는 내용이 거의 없는 최소한의 HTML과 JS 파일 링크만 응답합니다. 브라우저가 HTML을 받은 뒤 JS를 요청하고, JS 엔진이 실행되고, React 같은 라이브러리가 DOM을 조작해 콘텐츠가 화면에 표시됩니다.&lt;/p&gt;

&lt;p&gt;HTML을 받은 이후부터 콘텐츠가 보이기까지 필요한 절차가 많은 만큼 흰 화면이나 로딩 UI가 표시됩니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxummh8m7u6fqj8iygb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxummh8m7u6fqj8iygb.png" alt="CSR 렌더링 프로세스" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="https://prismic.io/blog/client-side-vs-server-side-rendering#what-is-clientside-rendering" rel="noopener noreferrer"&gt;https://prismic.io/blog/client-side-vs-server-side-rendering#what-is-clientside-rendering&lt;/a&gt;



&lt;h3&gt;
  
  
  SSR (Server-Side Rendering)
&lt;/h3&gt;

&lt;p&gt;서버에서 HTML을 미리 완성해 브라우저에 전달하는 방식입니다.&lt;/p&gt;

&lt;p&gt;서버에서 렌더링한다는 것은 시각적으로 그린다는 게 아니라, 필요한 데이터를 포함한 완성된 HTML 문자열을 만들어 보낸다는 것을 의미합니다.&lt;/p&gt;

&lt;p&gt;브라우저는 받은 HTML을 파싱해 로딩없이 바로 콘텐츠를 표시할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumvlmj6vefzig15wxkrg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumvlmj6vefzig15wxkrg.jpeg" alt="SSR 렌더링 프로세스" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="https://prismic.io/blog/client-side-vs-server-side-rendering#what-is-serverside-rendering" rel="noopener noreferrer"&gt;https://prismic.io/blog/client-side-vs-server-side-rendering#what-is-serverside-rendering&lt;/a&gt;






&lt;h2&gt;
  
  
  "CSR이 느리다"는 말이 헷갈리는 이유
&lt;/h2&gt;

&lt;p&gt;SSR 방식은 완성된 HTML을 응답하는 정적 SSR과 요청한 시점에 HTML을 생성해 응답하는 동적 SSR으로 나눌 수 있습니다.&lt;/p&gt;

&lt;p&gt;동적 SSR의 경우 외부 API를 호출하거나 DB 쿼리가 느리면, 데이터를 받아 HTML을 생성하기까지 전체 페이지 로드가 지연되게 됩니다.&lt;/p&gt;

&lt;p&gt;반면 CSR은 서버가 빈 HTML을 응답하고 JS가 실행된 후 브라우저에서 데이터를 불러오기 때문에, 데이터가 느리게 오더라도 그 사이에 로딩 UI를 보여줄 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;그렇다면 CSR이 SSR보다 빠른 걸까요?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;결론부터 말하면 기준에 따라 달라집니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpmfk2cs594psvo68qit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpmfk2cs594psvo68qit.png" alt="주요 이벤트 타임라인 그래프" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;초기 로딩을 HTML 응답 ~ 화면 표시 시점으로 보면, JS 처리 시간이 소요되기 때문에 CSR이 SSR에 비해 느리다고 할 수 있습니다.

&lt;ul&gt;
&lt;li&gt;일반적으로 말하는 초기 로딩이 이 구간에 해당한다고 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;초기 로딩을 페이지 요청 ~ 화면 표시 시점으로 보면, 서버 데이터 fetch 작업이 CSR의 JS 처리 시간보다 길어지게 될 경우 SSR이 CSR에 비해 느릴 수 있습니다.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  TTFB / FCP / LCP로 보는 CSR vs SSR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  웹 바이탈 지표
&lt;/h3&gt;

&lt;p&gt;웹 바이탈 지표는 웹의 품질을 수치화하고 개선하는데 참고할 수 있는 지표입니다.&lt;br&gt;
렌더링 방식의 속도를 비교할 때 이러한 지표로 보면 더욱 명확해집니다.&lt;br&gt;
이 글에서는 시간과 관련된 세가지 지표를 보겠습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TTFB (Time to First Byte)&lt;/strong&gt;: 페이지를 요청한 시점부터 서버 응답의 첫 번째 바이트를 받는 시점까지의 시간&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsczs2nunite24fp2wsx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsczs2nunite24fp2wsx.png" alt="TTFB" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;
startTime와 responseStart 사이의 시간입니다.
    &lt;a href="https://web.dev/articles/ttfb?hl=ko" rel="noopener noreferrer"&gt;https://web.dev/articles/ttfb?hl=ko&lt;/a&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FCP (First Contentful Paint)&lt;/strong&gt;: 페이지를 요청한 시점부터 콘텐츠의 일부가 화면에 처음 표시되는 시점까지의 시간&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4lprgv9fqjzwkemengt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4lprgv9fqjzwkemengt.png" alt="FCP" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="https://web.dev/articles/fcp?hl=ko" rel="noopener noreferrer"&gt;https://web.dev/articles/fcp?hl=ko&lt;/a&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LCP (Largest Contentful Paint)&lt;/strong&gt;: 페이지를 요청한 시점부터 주요 콘텐츠(가장 큰 이미지나 텍스트 블록)가 화면에 표시되는 시점까지의 시간&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4key0rsohtrio6gd7u2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4key0rsohtrio6gd7u2.png" alt="LCP" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="https://web.dev/articles/lcp?hl=ko" rel="noopener noreferrer"&gt;https://web.dev/articles/lcp?hl=ko&lt;/a&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CSR과 SSR 지표 비교
&lt;/h3&gt;

&lt;h4&gt;
  
  
  실험 설계
&lt;/h4&gt;

&lt;p&gt;이 글의 측정 결과는 아래 조건의 데모 앱을 기반으로 합니다. (&lt;a href="https://github.com/o5sy/blog/tree/main/web/csr_vs_ssr/demo" rel="noopener noreferrer"&gt;소스코드&lt;/a&gt;)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;스택&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSR&lt;/td&gt;
&lt;td&gt;React 19 + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR&lt;/td&gt;
&lt;td&gt;Next.js 16 App Router (dynamic SSR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 서버&lt;/td&gt;
&lt;td&gt;Express, &lt;code&gt;GET /api/posts?delay=&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;두 앱 모두 같은 API 서버를 바라보며, &lt;code&gt;delay&lt;/code&gt; 쿼리 파라미터로 응답 지연 시간을 조절할 수 있습니다. 렌더링 방식별 차이를 명확히 드러내기 위해 2000ms의 지연을 의도적으로 설정했습니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  CSR
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gyke9cvz08ugjo5qqsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gyke9cvz08ugjo5qqsm.png" alt="CSR LCP 퍼포먼스 타임라인" width="800" height="586"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuozmas2i29z2rwyl6z19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuozmas2i29z2rwyl6z19.png" alt="CSR 타임라인 그래프" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;시간&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTFB&lt;/td&gt;
&lt;td&gt;13ms&lt;/td&gt;
&lt;td&gt;빈 HTML을 즉시 반환하므로 매우 빠릅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FCP&lt;/td&gt;
&lt;td&gt;516ms&lt;/td&gt;
&lt;td&gt;HTML 수신 → JS 요청 → JS 실행 → DOM 조작 순서를 거쳐야 첫 콘텐츠가 표시됩니다. JS 번들이 커질수록 길어질 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCP&lt;/td&gt;
&lt;td&gt;2.52s&lt;/td&gt;
&lt;td&gt;JS 실행 이후 API를 호출하기 때문에, API 지연(2000ms)이 그대로 반영됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TTFB가 매우 빠르고, FCP는 JS 처리 시간만큼, LCP는 API 지연까지 더해져 느려집니다.&lt;/p&gt;

&lt;p&gt;일반적으로 LCP의 체감 시간을 줄이기 위해 로딩 UI(스켈레톤, 스피너 등)를 표시합니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  SSR
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn7jn3wyczxp1zu4z0ie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsn7jn3wyczxp1zu4z0ie.png" alt="SSR LCP 퍼포먼스 타임라인" width="800" height="586"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrkey8xyijnf9cyxsnmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrkey8xyijnf9cyxsnmh.png" alt="SSR 타임라인 그래프" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;시간&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTFB&lt;/td&gt;
&lt;td&gt;2.02s&lt;/td&gt;
&lt;td&gt;정적 SSR에선 빠르지만, 동적 SSR에서 데이터 fetch 등 작업이 많을수록 느려질 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FCP&lt;/td&gt;
&lt;td&gt;2.42s&lt;/td&gt;
&lt;td&gt;HTML을 받으면 바로 콘텐츠를 표시할 수 있어, TTFB 직후 빠르게 렌더링됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCP&lt;/td&gt;
&lt;td&gt;2.42s&lt;/td&gt;
&lt;td&gt;주요 콘텐츠가 이미 HTML에 포함되어 있어 FCP와 거의 동시에 표시됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FCP와 LCP가 빠른 대신 TTFB가 서버 처리 속도에 영향을 받아 느려질 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  기준에 따른 비교 설명
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;TTFB 기준: 크게 차이 안나지만, 동적 SSR은 서버 작업이 길어질 경우 CSR보다 느려질 수 있습니다.&lt;/li&gt;
&lt;li&gt;FCP 기준: 일반적으로 SSR이 빠릅니다. 단, SSR의 TTFB가 CSR의 JS 처리 시간보다 길면 역전될 수 있습니다.&lt;/li&gt;
&lt;li&gt;LCP 기준: 일반적으로 SSR이 빠릅니다. CSR에서 주요 콘텐츠가 초기 렌더링에 포함되지 않는다면 워터폴로 인해 늦어질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;이제 렌더링 방식의 속도를 기준점에 따라 명확하게 설명할 수 있게 됐습니다.&lt;/p&gt;

&lt;p&gt;"CSR이 초기 로딩이 느리다"는 것도 HTML 응답부터 첫 콘텐츠 렌더링에 걸린 시간(FCP에서 TTFB를 뺀 시간)으로 보면 그렇다고 할 수 있습니다. 돌아보면 이 말이 드러내고자 했던 건 CSR에 JS 처리 과정이 있다는 점이라고 생각됩니다. 충분한 맥락과 기준점 없이 쓰이다 보니 헷갈리는 요소가 된 것입니다.&lt;/p&gt;

&lt;p&gt;"CSR은 초기 로딩 느림, SSR은 빠름"으로만 기억한다면 초기 로딩이 느릴 때 렌더링 방식을 바꾸는 것만을 떠올리게 됩니다. 하지만 세부 맥락을 이해하면 원인을 좁혀 다양한 최적화 방안을 고려할 수 있습니다.&lt;/p&gt;

&lt;p&gt;TTFB가 느리다면 CDN과 캐싱 전략을, FCP가 느리다면 코드 스플리팅과 번들 사이즈 최적화 등을 검토할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이처럼 일반적인 사실을 그대로 받아들이기보다 숨겨진 맥락을 파악하는 게 중요합니다. 그래야 문제가 생겼을 때 적합한 선택지를 고를 수 있기 때문입니다.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;참고&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://prismic.io/blog/client-side-vs-server-side-rendering" rel="noopener noreferrer"&gt;Client-side vs. Server-side Rendering&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://lurgi.github.io/Development/Enhancing-UX-with-SSR" rel="noopener noreferrer"&gt;SSR로 사용자 경험 향상시키기&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hwanheejung.tistory.com/67" rel="noopener noreferrer"&gt;리액트의 렌더링 전략&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://web.dev/vitals?hl=ko" rel="noopener noreferrer"&gt;Web Vitals — web.dev&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://web.dev/articles/ttfb?hl=ko" rel="noopener noreferrer"&gt;Time to First Byte (TTFB)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://web.dev/articles/fcp?hl=ko" rel="noopener noreferrer"&gt;First Contentful Paint (FCP)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://web.dev/articles/lcp?hl=ko" rel="noopener noreferrer"&gt;Largest Contentful Paint (LCP)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://theodorusclarence.com/blog/nextjs-fetch-method" rel="noopener noreferrer"&gt;Understanding Next.js Data Fetching (CSR, SSR, SSG, ISR)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://theodorusclarence.com/blog/nextjs-fetch-usecase#introduction" rel="noopener noreferrer"&gt;How to choose between Next.js CSR, SSR, SSG, and ISR&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
