<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[RSS Feed of AI 시대 개발자 도구 블로그 | JavaPark]]></title><description><![CDATA[AI 시대의 개발자 도구, 실전 경험을 공유합니다]]></description><link>https://blog.javapark.kr</link><generator>GatsbyJS</generator><lastBuildDate>Sat, 09 May 2026 08:40:29 GMT</lastBuildDate><item><title><![CDATA[ORA-28547 에러 완벽 해결 가이드 — 공식 원인과 11g·19c 호환 해결법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Connection to server failed, probable Oracle Net admin error…]]></description><link>https://blog.javapark.kr/ora-28547-error-handling/</link><guid isPermaLink="false">https://blog.javapark.kr/ora-28547-error-handling/</guid><pubDate>Sat, 09 May 2026 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Connection to server failed, probable Oracle Net admin error&quot; — 검색해보면 사례가 워낙 다양해서 정답이 안 보이는 그 메시지요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;오라클을 다루다 보면 한 번쯤 마주치는 ORA-28547. 솔직히 말하면, 인터넷에 떠도는 트러블슈팅 글들이 &lt;strong&gt;공식 원인과 일반 연결 문제를 뒤섞어 놓은 경우가 많습니다.&lt;/strong&gt; 이 글은 &lt;a href=&quot;https://docs.oracle.com/en/error-help/db/ora-28547/&quot;&gt;Oracle 공식 문서&lt;/a&gt;를 기준으로 진짜 ORA-28547의 원인만 정리했습니다. 결론부터 말씀드리면 — 공식 원인은 5가지로 좁혀지고, &lt;strong&gt;그 외에 자주 거론되는 항목들은 ORA-28547과 다른 에러 코드의 문제일 가능성이 높습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 글은 &lt;strong&gt;Oracle 11g R2부터 19c까지&lt;/strong&gt; 동일하게 적용되며, 버전별로 다른 부분은 별도 표로 정리했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ORA-28547 에러란? — 공식 정의&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ORA-28547&lt;/strong&gt;: connection to server failed, probable Oracle Net admin error&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;공식 원인&lt;/strong&gt; (Oracle Database Error Messages 인용):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A failure occurred during initialization of a network connection from a client process to the Oracle server. The connection was completed but a disconnect occurred while trying to perform protocol-specific initialization, usually due to use of different network protocols by opposite sides of the connection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;쉽게 말하면 &lt;strong&gt;TCP 연결은 됐는데, 그 위에서 Oracle Net 프로토콜 협상이 깨진&lt;/strong&gt; 상태입니다. 인증(ID/비밀번호) 단계 이전에 발생하므로 ORA-01017(invalid username/password)이나 ORA-12541(no listener)과는 결이 다릅니다.&lt;/p&gt;
&lt;p&gt;비유하자면, 전화는 연결됐는데 &lt;strong&gt;두 사람이 서로 다른 언어로 인사하다가 통화가 끊긴 상황&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ORA-28547 공식 원인 5가지 (Oracle Docs 기준)&lt;/h2&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;1&lt;/td&gt;
&lt;td&gt;connect string에 &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt; 잘못 포함&lt;/td&gt;
&lt;td&gt;11g~19c 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Database link / 외부 프로시저용 HS 에이전트 설정 오류&lt;/td&gt;
&lt;td&gt;11g~19c 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;클라이언트가 서버 character set 미인식 (&lt;code class=&quot;language-text&quot;&gt;ORA_NLS10&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;11g~19c 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Windows + DRCP + &lt;code class=&quot;language-text&quot;&gt;SQLNET.AUTHENTICATION_SERVICES=NTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11g R1~19c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Shared server 전부 busy + &lt;code class=&quot;language-text&quot;&gt;INBOUND_CONNECT_TIMEOUT&lt;/code&gt; 만료&lt;/td&gt;
&lt;td&gt;11g~19c 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 5가지가 &lt;strong&gt;공식 문서가 명시한 ORA-28547의 진짜 원인&lt;/strong&gt;입니다. 비트 불일치, tnsnames 오타, 방화벽 차단 같은 항목은 다른 에러 코드의 영역이며, 본문 아래쪽 &lt;a href=&quot;#%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C--ora-28547%EB%A1%9C-%EC%98%A4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%AC%EC%9A%B4-%EA%B2%83%EB%93%A4&quot;&gt;헷갈리는 다른 에러 코드&lt;/a&gt; 섹션에서 별도로 정리했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ORA-28547 단계별 해결법&lt;/h2&gt;
&lt;h3&gt;1단계 — &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt; 점검 (가장 흔함)&lt;/h3&gt;
&lt;p&gt;연결 문자열이나 tnsnames.ora에 &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt;가 들어 있다면 Heterogeneous Services 에이전트로 라우팅됩니다. 일반 Oracle DB 접속에는 들어가면 안 되는 값입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ini&quot;&gt;&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 잘못된 예 — 일반 DB 연결인데 HS= 가 들어감&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ORCL&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token key attr-name&quot;&gt;(DESCRIPTION&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token key attr-name&quot;&gt;(ADDRESS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;(PROTOCOL = TCP)(HOST = 192.168.0.10)(PORT = 1521))&lt;/span&gt;
    &lt;span class=&quot;token key attr-name&quot;&gt;(CONNECT_DATA&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;token key attr-name&quot;&gt;(SID&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;ORCL)&lt;/span&gt;
    )
    &lt;span class=&quot;token key attr-name&quot;&gt;(HS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;OK)         # ← 이 라인을 지워야 합니다&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;ul&gt;
&lt;li&gt;Heterogeneous Services를 의도적으로 쓰는 게 아니라면 &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt; 라인 제거&lt;/li&gt;
&lt;li&gt;의도해서 쓴다면 HS 에이전트의 &lt;code class=&quot;language-text&quot;&gt;init&amp;lt;sid&gt;.ora&lt;/code&gt;(예: &lt;code class=&quot;language-text&quot;&gt;initdg4odbc.ora&lt;/code&gt;)와 &lt;code class=&quot;language-text&quot;&gt;listener.ora&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;PROGRAM=hsodbc&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;PROGRAM=dg4odbc&lt;/code&gt; 항목을 점검&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2단계 — Character Set 확인&lt;/h3&gt;
&lt;p&gt;클라이언트의 &lt;code class=&quot;language-text&quot;&gt;ORA_NLS10&lt;/code&gt; 환경변수가 잘못 설정됐거나, 서버에 새로 추가된/사용자 정의 character set을 클라이언트가 인식 못 하는 경우입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- 서버 character set 확인&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; nls_database_parameters
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; parameter &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;NLS_CHARACTERSET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NLS_NCHAR_CHARACTERSET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 클라이언트에서 ORA_NLS10 변수 확인&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Windows&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; %ORA_NLS10%

&lt;span class=&quot;token comment&quot;&gt;# Linux&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$ORA_NLS10&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;ul&gt;
&lt;li&gt;클라이언트 &lt;code class=&quot;language-text&quot;&gt;ORA_NLS10&lt;/code&gt;을 비워두거나 올바른 경로로 지정 (대부분 비워두는 게 정답)&lt;/li&gt;
&lt;li&gt;클라이언트 버전이 너무 낮으면 서버 character set을 못 따라가므로 &lt;strong&gt;클라이언트 업그레이드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용자 정의 character set을 쓴다면 클라이언트 쪽에도 동일하게 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3단계 — Windows DRCP + NTS 점검&lt;/h3&gt;
&lt;p&gt;DRCP(Database Resident Connection Pool)를 Windows에서 사용하면서 &lt;code class=&quot;language-text&quot;&gt;SQLNET.AUTHENTICATION_SERVICES=NTS&lt;/code&gt;인 경우 ORA-28547이 발생합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ini&quot;&gt;&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# sqlnet.ora — Windows DRCP 환경&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;SQLNET.AUTHENTICATION_SERVICES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;(NONE)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;버전 호환:&lt;/strong&gt; DRCP는 &lt;strong&gt;Oracle 11g R1(11.1)에서 도입&lt;/strong&gt;되었습니다. 11g~19c 모든 버전에서 같은 증상이 발생할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4단계 — Shared Server 자원·타임아웃 확인&lt;/h3&gt;
&lt;p&gt;Shared server 모드에서 모든 shared server가 busy 상태일 때, &lt;code class=&quot;language-text&quot;&gt;SQLNET.INBOUND_CONNECT_TIMEOUT&lt;/code&gt;(기본 60초, 11g 이후 동일)이 만료되면 ORA-28547로 떨어집니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- shared server 사용 현황&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; network&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; idle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; busy
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; v$dispatcher&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; busy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; idle
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; v$shared_server&lt;span class=&quot;token punctuation&quot;&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;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SHARED_SERVERS&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;MAX_SHARED_SERVERS&lt;/code&gt; 파라미터 상향&lt;/li&gt;
&lt;li&gt;또는 tnsnames.ora의 &lt;code class=&quot;language-text&quot;&gt;(CONNECT_DATA = ... (SERVER = DEDICATED) ...)&lt;/code&gt; 명시로 dedicated 강제&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;sqlnet.ora&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;SQLNET.INBOUND_CONNECT_TIMEOUT_LISTENER&lt;/code&gt; 값을 늘려 일시적 회피 (근본 해결은 자원 확보)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5단계 — alert.log·trace에서 동반 에러 찾기&lt;/h3&gt;
&lt;p&gt;서버의 &lt;code class=&quot;language-text&quot;&gt;$ORACLE_BASE/diag/rdbms/&amp;lt;DB&gt;/&amp;lt;SID&gt;/trace/alert_&amp;lt;SID&gt;.log&lt;/code&gt;(11g R1 이상, ADR 도입 후)에 ORA-28547과 함께 추가 단서가 찍힌 경우가 많습니다. 10g 이하·11g 이전 환경은 &lt;code class=&quot;language-text&quot;&gt;$ORACLE_HOME/admin/&amp;lt;SID&gt;/bdump/alert_&amp;lt;SID&gt;.log&lt;/code&gt; 경로입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;버전별 호환표 — 11g · 12c · 19c&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;11g R2&lt;/th&gt;
&lt;th&gt;12c&lt;/th&gt;
&lt;th&gt;19c&lt;/th&gt;
&lt;th&gt;운영 영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ORA-28547 정의·공식 원인 5가지&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;td&gt;본문 1~5단계 모두 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증 파라미터&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;SQLNET.ALLOWED_LOGON_VERSION&lt;/code&gt; (단일)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;_SERVER&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;_CLIENT&lt;/code&gt; 분리, 단일은 deprecated&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;_SERVER&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;_CLIENT&lt;/code&gt; 권장, 단일은 desupport 대상&lt;/td&gt;
&lt;td&gt;12c 이상 업그레이드 시 파라미터 이름 교체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DRCP&lt;/td&gt;
&lt;td&gt;11.1부터 사용 가능&lt;/td&gt;
&lt;td&gt;사용 가능&lt;/td&gt;
&lt;td&gt;사용 가능&lt;/td&gt;
&lt;td&gt;본문 3단계 동일 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alert.log 경로&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;diag/rdbms/...&lt;/code&gt; (ADR)&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;td&gt;11g 이전은 &lt;code class=&quot;language-text&quot;&gt;bdump/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premier / Extended Support&lt;/td&gt;
&lt;td&gt;11.2.0.4: &lt;strong&gt;2020-12 종료&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.2: &lt;strong&gt;2022-03 종료&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19c: 2027-04 / 2032-04 (예정)&lt;/td&gt;
&lt;td&gt;11g·12c 운영 시 보안 패치 부재&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;11g 운영자 주의:&lt;/strong&gt; Oracle 11.2.0.4의 모든 패치 지원은 &lt;strong&gt;2020년 12월에 종료&lt;/strong&gt;됐습니다. ORA-28547 자체는 본 가이드대로 동일하게 해결되지만, &lt;strong&gt;OS·네트워크 보안 패치 갱신이 멈춰 있으므로 19c 마이그레이션을 권장&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;테스트·개발용 Oracle 도커 이미지 추천 (2026-05 기준)&lt;/h2&gt;
&lt;p&gt;ORA-28547을 로컬에서 재현하거나 버전별 동작을 검증할 때 어떤 도커 이미지를 쓰는 게 좋은지 용도별로 정리했습니다.&lt;/p&gt;
&lt;h3&gt;용도별 권장 이미지&lt;/h3&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;빠른 로컬 개발 / 최신 기능 검증&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;container-registry.oracle.com/database/free&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Oracle 공식, 23ai Free, 무료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;멀티 아치 (Apple Silicon · ARM64)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-free&lt;/code&gt; (23.5+)&lt;/td&gt;
&lt;td&gt;Oracle 직원이 메인테인, 멀티 플랫폼 빌드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19c · 21c 엔터프라이즈 동작 검증&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;container-registry.oracle.com/database/enterprise&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Oracle 계정·라이선스 동의 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11g R2 레거시 호환 검증&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-xe:11.2.0.2-slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86_64 전용, ARM은 에뮬레이션 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디스크 절약·빠른 시작&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-free:slim&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;:latest-lite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;슬림 빌드, 일부 기능 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;23ai Free — 현재 가장 권장 (라이선스·아키 모두 자유로움)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; oracle-free &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1521&lt;/span&gt;:1521 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ORACLE_PWD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;YourSecurePassword &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  container-registry.oracle.com/database/free:latest-lite

&lt;span class=&quot;token comment&quot;&gt;# 접속&lt;/span&gt;
sqlplus sys/YourSecurePassword@localhost:1521/FREEPDB1 as sysdba&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;서비스명은 &lt;code class=&quot;language-text&quot;&gt;FREE&lt;/code&gt;(CDB) 또는 &lt;code class=&quot;language-text&quot;&gt;FREEPDB1&lt;/code&gt;(기본 PDB). ORA-28547 재현/검증을 위해 sqlnet.ora를 손봐야 한다면 &lt;code class=&quot;language-text&quot;&gt;/opt/oracle/oradata/dbconfig/FREE/&lt;/code&gt; 경로를 볼륨 마운트하세요.&lt;/p&gt;
&lt;h3&gt;Apple Silicon · ARM64 환경에서 주의&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;23ai Free (23.5 이상)&lt;/strong&gt; — 멀티 아치 이미지라 그대로 사용 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;11g / 18c / 21c XE&lt;/strong&gt; — Oracle DB는 ARM 네이티브 포트가 없으므로 &lt;a href=&quot;https://github.com/abiosoft/colima&quot;&gt;colima&lt;/a&gt; 등으로 x86_64 에뮬레이션 필요:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# colima로 x86_64 VM 띄우고 그 위에서 실행&lt;/span&gt;
colima start &lt;span class=&quot;token parameter variable&quot;&gt;--arch&lt;/span&gt; x86_64 &lt;span class=&quot;token parameter variable&quot;&gt;--memory&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; oracle-xe-11g &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1521&lt;/span&gt;:1521 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ORACLE_PASSWORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;YourSecurePassword &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  gvenzl/oracle-xe:11.2.0.2-slim&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;운영 환경에서의 주의&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Oracle 공식 엔터프라이즈 이미지(&lt;code class=&quot;language-text&quot;&gt;container-registry.oracle.com/database/enterprise&lt;/code&gt;)는 &lt;strong&gt;상용 라이선스 동의가 필요&lt;/strong&gt;합니다. 운영 용도라면 사내 라이선스 정책 확인 필수.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-xe&lt;/code&gt; 같은 커뮤니티 XE 이미지는 &lt;strong&gt;개발·테스트 전용&lt;/strong&gt;으로만 사용하세요. 운영은 공식 채널을 통한 라이선스된 환경에서.&lt;/li&gt;
&lt;li&gt;ORA-28547 시나리오 중 &lt;strong&gt;DRCP·Shared Server 재현은 Free 이미지에서도 가능&lt;/strong&gt;하지만 별도 파라미터 설정이 필요합니다 (&lt;code class=&quot;language-text&quot;&gt;ALTER SYSTEM SET shared_servers = ...&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;한글 지원 — 어떤 character set을 쓸까&lt;/h3&gt;
&lt;p&gt;Oracle DB의 한글 처리는 character set 선택이 핵심입니다. 본문 &lt;a href=&quot;#2%EB%8B%A8%EA%B3%84--character-set-%ED%99%95%EC%9D%B8&quot;&gt;2단계 — Character Set 확인&lt;/a&gt;에서 다뤘듯 &lt;strong&gt;클라이언트가 서버 character set을 못 따라가면 ORA-28547로 떨어질 수 있으므로&lt;/strong&gt;, 도커 이미지를 고를 때부터 신경 써두는 게 좋습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Character Set&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;&lt;strong&gt;AL32UTF8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;신규 환경 / 다국어 지원&lt;/td&gt;
&lt;td&gt;Oracle 공식 권장, 23ai Free 기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KO16MSWIN949&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;레거시 운영 DB(Windows 한글) 미러링&lt;/td&gt;
&lt;td&gt;빌드 시점에 변경 가능한 이미지 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KO16KSC5601&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매우 오래된 레거시 호환&lt;/td&gt;
&lt;td&gt;신규 시스템에선 비추천&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;케이스 1 — AL32UTF8 (대부분의 신규 환경, 권장)&lt;/h4&gt;
&lt;p&gt;23ai Free 이미지는 &lt;strong&gt;AL32UTF8이 기본값&lt;/strong&gt;입니다. 별도 설정 없이 그대로 쓰면 한글이 정상 처리됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; oracle-free &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1521&lt;/span&gt;:1521 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ORACLE_PWD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;YourSecurePassword &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  container-registry.oracle.com/database/free:latest-lite&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; nls_database_parameters
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; parameter &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;NLS_CHARACTERSET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NLS_NCHAR_CHARACTERSET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- NLS_CHARACTERSET = AL32UTF8&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;케이스 2 — KO16MSWIN949 (레거시 운영 DB 미러링)&lt;/h4&gt;
&lt;p&gt;운영 DB가 KO16MSWIN949로 돌고 있어 로컬에서 동일 환경 재현이 필요한 경우입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;이미지 선택 주의:&lt;/strong&gt; 23ai Free 및 Oracle 공식 docker-images의 23c Free 이미지는 &lt;code class=&quot;language-text&quot;&gt;ORACLE_CHARACTERSET&lt;/code&gt; 환경변수를 줘도 AL32UTF8로 고정되는 알려진 제약이 있습니다(&lt;a href=&quot;https://github.com/oracle/docker-images/issues/2673&quot;&gt;GitHub Issue #2673&lt;/a&gt;). 한글 KO16MSWIN949 환경을 재현하려면 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-xe&lt;/code&gt;처럼 빌드 시점에 character set을 받아주는 이미지&lt;/strong&gt;를 사용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; oracle-xe-kr &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1521&lt;/span&gt;:1521 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ORACLE_PASSWORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;YourSecurePassword &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ORACLE_CHARACTERSET&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;KO16MSWIN949 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  gvenzl/oracle-xe:21-slim-faststart&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Apple Silicon에선 &lt;code class=&quot;language-text&quot;&gt;gvenzl/oracle-xe&lt;/code&gt;가 ARM 미지원이라 colima로 x86_64 에뮬레이션이 필요합니다(앞의 ARM64 섹션 참고).&lt;/p&gt;
&lt;h4&gt;클라이언트 NLS_LANG 설정 — 한글 깨짐 방지&lt;/h4&gt;
&lt;p&gt;컨테이너 character set과 별개로, &lt;strong&gt;외부 클라이언트에서 한글을 입출력할 때&lt;/strong&gt;는 &lt;code class=&quot;language-text&quot;&gt;NLS_LANG&lt;/code&gt; 환경변수가 핵심입니다.&lt;/p&gt;
&lt;p&gt;자주 헷갈리는 포인트인데, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;NLS_LANG&lt;/code&gt;의 character set 부분은 서버가 아니라 &quot;클라이언트 터미널/IDE의 실제 표시 인코딩&quot;과 일치&lt;/strong&gt;시켜야 합니다. 매칭이 어긋나면 Oracle이 잘못된 변환을 하고, 한글이 &lt;code class=&quot;language-text&quot;&gt;???&lt;/code&gt;로 깨집니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;클라이언트 환경&lt;/th&gt;
&lt;th&gt;권장 NLS_LANG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS · Linux 터미널 (UTF-8)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;KOREAN_KOREA.AL32UTF8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows 명령 프롬프트 (cp949)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;KOREAN_KOREA.KO16MSWIN949&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDBC 애플리케이션&lt;/td&gt;
&lt;td&gt;미설정 OK (드라이버가 자동 처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Linux / macOS&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;NLS_LANG&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;KOREAN_KOREA.AL32UTF8

&lt;span class=&quot;token comment&quot;&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:NLS_LANG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;KOREAN_KOREA.KO16MSWIN949&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Windows cmd&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;NLS_LANG&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;KOREAN_KOREA.KO16MSWIN949&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;서버 character set이 다르더라도 Oracle Net이 자동으로 변환해주므로, &lt;strong&gt;클라이언트 표시 인코딩 기준으로 맞추는 게 정답&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h4&gt;Instant Client 패키지 — Basic vs Basic Light&lt;/h4&gt;
&lt;p&gt;외부에서 Oracle 컨테이너에 접속할 때 쓰는 Oracle Instant Client는 &lt;strong&gt;Basic&lt;/strong&gt;과 &lt;strong&gt;Basic Light(basiclite)&lt;/strong&gt; 두 가지로 배포됩니다. 한글 환경이라면 어느 쪽을 받느냐가 character set 호환성과 직결되므로 신중히 골라야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Basic&lt;/th&gt;
&lt;th&gt;Basic Light (basiclite)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;패키지 크기&lt;/td&gt;
&lt;td&gt;큼 (~200MB)&lt;/td&gt;
&lt;td&gt;작음 (~40MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 메시지 언어&lt;/td&gt;
&lt;td&gt;전체 언어&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;영어만&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지원 character set&lt;/td&gt;
&lt;td&gt;약 250개 (전체)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8개로 제한&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AL32UTF8 (Unicode)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KO16MSWIN949 / KO16KSC5601&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ → &lt;strong&gt;ORA-12737 발생&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LANGUAGE / TERRITORY 자유 설정&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Basic Light가 지원하는 character set은 다음 8개로 한정되며, &lt;strong&gt;그 외 character set 서버에 접속하면 ORA-12737 에러&lt;/strong&gt;가 납니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;US7ASCII, WE8DEC, WE8MSWIN1252, WE8ISO8859P1,
WE8EBCDIC37C, WE8EBCDIC1047, UTF8, AL32UTF8&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;시나리오별 권장:&lt;/strong&gt;&lt;/p&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;서버가 AL32UTF8 (대부분의 신규 환경)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Basic Light&lt;/strong&gt; OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버가 KO16MSWIN949 (레거시 한글 운영 DB)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Basic 필수&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컨테이너 이미지 크기 최소화 우선&lt;/td&gt;
&lt;td&gt;Basic Light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;한글 에러 메시지 / 다국어 메시지 필요&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ORA-12737 주의:&lt;/strong&gt; Basic Light로 KO16MSWIN949 서버에 접속하면 &lt;code class=&quot;language-text&quot;&gt;ORA-12737: Instant Client Light: unsupported server character set&lt;/code&gt; 에러가 납니다. NLS_LANG 조정으로는 회피되지 않으며, &lt;strong&gt;풀 Basic 패키지로 교체&lt;/strong&gt;가 유일한 해결입니다. 본문 ORA-28547과는 별개 에러이지만 한글 환경에서 자주 함께 등장합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;도커 이미지 안에서 sqlplus 등을 실행해야 한다면, Instant Client를 포함한 멀티스테이지 이미지를 빌드할 때 character set 매칭에 따라 Basic / Basic Light를 선택하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;헷갈리는 다른 에러 코드 — ORA-28547로 오해하기 쉬운 것들&lt;/h2&gt;
&lt;p&gt;검색하면 ORA-28547과 같이 묶여 나오지만, &lt;strong&gt;공식적으로는 다른 에러 코드의 영역&lt;/strong&gt;인 항목들입니다. ORA-28547로 보이더라도 alert.log에 아래 코드가 함께 찍혔다면 &lt;strong&gt;진짜 원인은 그쪽&lt;/strong&gt;입니다.&lt;/p&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;32bit/64bit 클라이언트 비트 불일치&lt;/td&gt;
&lt;td&gt;ORA-12705 / ORA-12537&lt;/td&gt;
&lt;td&gt;NLS · 환경 부정합 / partner disconnected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tnsnames.ora 호스트·서비스명 오타&lt;/td&gt;
&lt;td&gt;ORA-12154 / ORA-12541&lt;/td&gt;
&lt;td&gt;TNS resolve 실패 / no listener&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리스너에 서비스 미등록&lt;/td&gt;
&lt;td&gt;ORA-12514&lt;/td&gt;
&lt;td&gt;listener does not currently know of service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12c 클라이언트로 19c 서버 접속 실패&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ORA-28040&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No matching authentication protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;방화벽이 1521 포트 차단&lt;/td&gt;
&lt;td&gt;ORA-12170 / ORA-12541&lt;/td&gt;
&lt;td&gt;TNS:Connect timeout / no listener&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버에서 갑자기 끊김&lt;/td&gt;
&lt;td&gt;ORA-12537 &lt;em&gt;(informational)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;partner disconnected (EOF) — 추가 단서 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;애플리케이션 레벨 처리 — 운영 권장 패턴&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Spring + Resilience4j 예시&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retryable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    maxAttempts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    backoff &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Backoff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiplier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Recover&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recover&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DB 연결 실패 (errorCode={})&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getErrorCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    alertService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notifyOps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DB connection failure: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getErrorCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceUnavailableException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DB 일시 점검 중입니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&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;ul&gt;
&lt;li&gt;ORA-28547처럼 일시적일 수 있는 에러(특히 shared server busy 시나리오)는 짧은 재시도로 흡수&lt;/li&gt;
&lt;li&gt;연속 실패 시 운영팀 알림, 사용자에게는 일반 메시지&lt;/li&gt;
&lt;li&gt;HikariCP의 &lt;code class=&quot;language-text&quot;&gt;connectionTestQuery&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;validationTimeout&lt;/code&gt;으로 사전 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;ORA-28547 자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q1. 운영 중인 서비스에서 갑자기 발생합니다. 가장 먼저 무엇을 봐야 하나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;서버의 &lt;code class=&quot;language-text&quot;&gt;alert.log&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;listener.log&lt;/code&gt;를 동시에 확인해 ORA-28547과 함께 찍힌 동반 에러 코드를 찾으세요. 위 &lt;a href=&quot;#%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C--ora-28547%EB%A1%9C-%EC%98%A4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%AC%EC%9A%B4-%EA%B2%83%EB%93%A4&quot;&gt;헷갈리는 다른 에러 코드&lt;/a&gt; 표에서 매칭되는 항목이 있다면 진짜 원인은 그쪽입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q2. tnsping은 성공하는데 sqlplus만 ORA-28547이 납니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;tnsping은 리스너까지의 네트워크와 TNS resolve만 확인합니다. ORA-28547은 그 이후 단계인 &lt;strong&gt;세션 협상&lt;/strong&gt;에서 깨지는 에러라 tnsping은 통과합니다. 본문 1·2·3·4단계를 차례로 점검하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q3. Database link 호출 시에만 ORA-28547이 납니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;가장 흔한 정공 케이스입니다. Database link가 가리키는 tnsnames 항목에 &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt;가 들어 있거나, HS 에이전트의 &lt;code class=&quot;language-text&quot;&gt;init&amp;lt;sid&gt;.ora&lt;/code&gt;가 잘못된 경우입니다. 본문 1단계를 우선 점검하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q4. 12c 이하 클라이언트로 19c 서버에 붙는데 인증 호환 에러가 납니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 시나리오는 ORA-28547이 아니라 &lt;strong&gt;ORA-28040 (No matching authentication protocol)&lt;/strong&gt; 입니다. 19c 서버의 &lt;code class=&quot;language-text&quot;&gt;SQLNET.ALLOWED_LOGON_VERSION_SERVER&lt;/code&gt; 기본값이 12로 올라갔기 때문입니다. 임시 해결은 서버에서 값을 11로 낮추는 것이고, 근본 해결은 클라이언트를 19c로 업그레이드하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q5. 11g 환경에서도 본 가이드를 그대로 적용해도 되나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ORA-28547의 5가지 공식 원인은 11g R2부터 19c까지 동일합니다. 다만 위 버전별 호환표에 정리한 대로 &lt;strong&gt;인증 파라미터 이름&lt;/strong&gt;(11g는 단일 &lt;code class=&quot;language-text&quot;&gt;SQLNET.ALLOWED_LOGON_VERSION&lt;/code&gt;)과 &lt;strong&gt;alert.log 경로&lt;/strong&gt;(11g R1 이전은 &lt;code class=&quot;language-text&quot;&gt;bdump/&lt;/code&gt;)는 다릅니다. 11.2.0.4는 2020-12에 모든 패치 지원이 종료됐으므로 보안 측면에서 19c 마이그레이션을 권장합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 공식 원인 중심 체크리스트&lt;/h2&gt;
&lt;p&gt;ORA-28547을 만났을 때 위에서부터 차례로 확인하세요.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; connect string · tnsnames에 &lt;code class=&quot;language-text&quot;&gt;(HS=)&lt;/code&gt;가 들어 있는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HS 에이전트(database link / 외부 프로시저)를 의도적으로 사용 중인가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 클라이언트 &lt;code class=&quot;language-text&quot;&gt;ORA_NLS10&lt;/code&gt; 환경변수가 비정상적으로 설정돼 있는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Windows + DRCP 환경에서 &lt;code class=&quot;language-text&quot;&gt;SQLNET.AUTHENTICATION_SERVICES=NTS&lt;/code&gt;인가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Shared server 모드에서 모든 server가 busy 상태인가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; alert.log에 동반 에러 코드가 함께 찍혀 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 6가지가 모두 아니라면, 위 &lt;a href=&quot;#%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C--ora-28547%EB%A1%9C-%EC%98%A4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%AC%EC%9A%B4-%EA%B2%83%EB%93%A4&quot;&gt;헷갈리는 다른 에러 코드&lt;/a&gt; 표로 넘어가서 ORA-28547이 진짜 원인인지부터 다시 확인하세요.&lt;/p&gt;
&lt;p&gt;여러분이 만난 ORA-28547은 어느 케이스였나요? 댓글로 alert.log에 함께 찍혔던 동반 에러 코드를 공유해주시면 다음 글에 사례로 반영하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고 자료&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/error-help/db/ora-28547/&quot;&gt;Oracle Database Error Messages — ORA-28547&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/error-help/db/ora-28040/&quot;&gt;Oracle Database Error Messages — ORA-28040&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/error-help/db/ora-12537/&quot;&gt;Oracle Database Error Messages — ORA-12537&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/netag/&quot;&gt;Oracle Net Services Administrator&apos;s Guide (19c)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/netrf/parameters-for-the-sqlnet.ora.html&quot;&gt;Parameters for the sqlnet.ora File (19c)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://oracle-base.com/articles/11g/database-resident-connection-pool-11gr1&quot;&gt;Database Resident Connection Pool (DRCP) in Oracle 11g R1 — ORACLE-BASE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf&quot;&gt;Oracle Lifetime Support Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.oracle.com/database/announcing-oracle-database-23ai-free-container-images-for-armbased-apple-macbook-computers&quot;&gt;Oracle Database 23ai Free — Apple Silicon ARM 지원 발표&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/gvenzl/oracle-free&quot;&gt;gvenzl/oracle-free Docker Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/gvenzl/oracle-xe&quot;&gt;gvenzl/oracle-xe Docker Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oracle/docker-images&quot;&gt;Oracle 공식 docker-images GitHub 리포지토리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oracle/docker-images/issues/2673&quot;&gt;GitHub Issue #2673 — Oracle 23c Free character set 변경 제약&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/database/technologies/faq-nls-lang.html&quot;&gt;Oracle NLS_LANG FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/18/ntcli/instant-client-light-language-and-character-set-requirements.html&quot;&gt;Oracle Instant Client Light — Language and Character Set Requirements (18c)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/ntcli/about-oracle-instant-client-light.html&quot;&gt;About Oracle Instant Client Basic Light (23c)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Claude 책 만들기 #2 — 콘셉트 잡기: 누가 읽을지부터 정한다]]></title><description><![CDATA[Claude 책 만들기 #2 — 콘셉트 잡기: 한 줄 요약·페르소나·약속으로 책의 중심 잡기 "쓰고 싶은 주제는 있는데, 막상 한 줄로 적어보라면 막혀요." 8주 챌린지 시작과 함께 가장 많이 받은 피드백입니다. 솔직히 저도 #1 시작 전 준비의…]]></description><link>https://blog.javapark.kr/book-making-02-concept/</link><guid isPermaLink="false">https://blog.javapark.kr/book-making-02-concept/</guid><pubDate>Mon, 04 May 2026 22:30:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/book-making-02-concept.png&quot; alt=&quot;Claude 책 만들기 #2 — 콘셉트 잡기: 한 줄 요약·페르소나·약속으로 책의 중심 잡기&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;쓰고 싶은 주제는 있는데, 막상 한 줄로 적어보라면 막혀요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;8주 챌린지 시작과 함께 가장 많이 받은 피드백입니다. 솔직히 저도 &lt;a href=&quot;/book-making/book-making-01-getting-started/&quot;&gt;#1 시작 전 준비&lt;/a&gt;의 5개 질문에 답하면서 &quot;내가 진짜 어떤 책을 쓰고 싶은 거지?&quot;를 두 번이나 다시 적었습니다.&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다. 「나만의 책만들기」 시리즈 &lt;strong&gt;#2 — 콘셉트 잡기&lt;/strong&gt; 시간입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;2장의 목표는 책의 중심을 단 세 줄로 못 박는 것&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;이 세 줄이 흔들리면 8주가 흔들립니다. 반대로 이 세 줄만 단단하면 나머지는 따라옵니다. 오늘은 Claude와 함께 이 세 줄을 잡는 워크플로우를 정리하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 콘셉트가 가장 먼저인가&lt;/h2&gt;
&lt;p&gt;빈 마크다운 파일 앞에 앉으면 바로 떠오르는 충동이 있습니다 — &lt;strong&gt;차례부터 짜고 싶다&lt;/strong&gt;. &quot;1장은 이거, 2장은 저거…&quot; 한 번에 차례를 만들어 두면 뭔가 시작한 기분이 듭니다.&lt;/p&gt;
&lt;p&gt;함정입니다. 차례는 &lt;strong&gt;콘셉트의 결과물&lt;/strong&gt;이지 출발점이 아닙니다.&lt;/p&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;챕터 제목이 모호함 (&quot;AI 활용&quot;, &quot;도구 소개&quot;)&lt;/td&gt;
&lt;td&gt;챕터마다 독자에게 줄 것이 명확&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8주 후 절반은 &quot;이거 빼야 하나?&quot;로 회의&lt;/td&gt;
&lt;td&gt;빼야 할 챕터가 처음부터 안 들어옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;표지·디자인 결정마다 &quot;톤이 뭐였더라?&quot;&lt;/td&gt;
&lt;td&gt;한 줄 요약이 디자인까지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;독자 피드백을 받아도 어디에 반영할지 모름&lt;/td&gt;
&lt;td&gt;페르소나가 &quot;이건 그 사람에게 필요한가?&quot; 답을 준다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;콘셉트 = 8주 동안 모든 결정의 기준선&lt;/strong&gt; 입니다. 차례·문체·표지·심지어 댓글에 어떻게 답할지까지 여기서 갈립니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1단계 — 한 줄 요약 (One-line Summary)&lt;/h2&gt;
&lt;h3&gt;좋은 한 줄 요약의 조건&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;h3&gt;나쁜 예 vs 좋은 예&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;나쁜 예 ❌&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;AI를 활용한 글쓰기에 관한 책&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— 누가 읽는지, 무엇을 얻는지 알 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;좋은 예 ✅&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;&lt;strong&gt;한 번도 책을 써본 적 없는 사람&lt;/strong&gt;이 &lt;strong&gt;Claude와 8주 동안&lt;/strong&gt; 한 챕터씩 써가며 &lt;strong&gt;자기만의 50p 전자책&lt;/strong&gt;을 손에 쥐는 책.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— 독자(처음 쓰는 사람), 도구(Claude), 기간(8주), 결과물(50p PDF)이 한 줄에 다 들어가 있습니다.&lt;/p&gt;
&lt;h3&gt;Claude 프롬프트 — 한 줄 요약 다듬기&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/book-making/book-making-01-getting-started/#%EC%8B%A4%EC%8A%B5-2--claude%EC%99%80-%EC%B2%AB-%EB%8C%80%ED%99%94-%EC%B1%85-%EC%BD%98%EC%85%89%ED%8A%B8-%EC%9D%B8%ED%84%B0%EB%B7%B0&quot;&gt;#1에서 받은 5개 질문 답변&lt;/a&gt;을 그대로 들고 가서 다음 프롬프트를 던집니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;다음은 내가 만들고 싶은 책에 대한 답변이다.

[지난 주 5개 답변 붙여넣기]

이 답변을 기반으로 책의 한 줄 요약 후보 5개를 만들어줘.
각 후보는 다음 조건을 모두 충족해야 한다:
&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; 50자 이내
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; 누구를 위한 책인지 명시
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; 읽으면 무엇을 얻는지 암시
&lt;span class=&quot;token list punctuation&quot;&gt;4.&lt;/span&gt; 같은 주제의 다른 책과 차별화 포인트 1개

5개를 표로 정리하고, 각 후보의 강점·약점도 한 줄씩 적어줘.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude가 5개 후보를 표로 줍니다. &lt;strong&gt;그중 가장 마음에 드는 것 1개를 고르거나, 두 개를 합쳐 다시 다듬으세요.&lt;/strong&gt; 다듬은 결과를 &lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt; 최상단에 박습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 5개를 한 번에 받아 비교하는 게 핵심입니다. &quot;괜찮은 한 줄&quot;을 단번에 만들려고 하지 마시고, &lt;strong&gt;선택지를 펼쳐놓고 고르는 작업&lt;/strong&gt;으로 바꾸세요. Claude가 가장 잘하는 일이 바로 이것입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;2단계 — 페르소나 (한 사람 정하기)&lt;/h2&gt;
&lt;h3&gt;&quot;모두를 위한 책&quot;의 함정&lt;/h3&gt;
&lt;p&gt;처음 책을 쓰는 사람이 가장 흔히 빠지는 함정 — &lt;strong&gt;&quot;누구나 읽을 수 있는 책&quot;&lt;/strong&gt; 으로 만들려는 것입니다. 결과는 정반대입니다. 누구를 위한지 모르는 책은 &lt;strong&gt;누구도 끝까지 읽지 않습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;해법은 단순합니다 — &lt;strong&gt;한 사람을 떠올리고 그 사람에게 말하듯 씁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;페르소나 카드 템플릿&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;book/research/notes/persona.md&lt;/code&gt; 파일을 만들고 다음을 채우세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; 페르소나 — 이 책의 첫 독자 한 사람&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 기본 정보&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 이름(가명): 김지영
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 나이: 34세
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 직업: 마케터 (회사원 7년 차)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 거주: 서울

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 책과 관련된 상황&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 글쓰기 경험: 회사 보고서·제안서 정도. 개인 글쓰기는 SNS만.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; AI 도구 경험: ChatGPT는 이메일 초안에 가끔 사용. Claude는 처음.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 책을 쓰고 싶은 이유: 자기 분야 노하우를 정리해서 이직·강의 자료로
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 가장 큰 두려움: &quot;분량을 채울 수 있을까?&quot;, &quot;AI가 다 써버린 책이라고 욕먹지 않을까?&quot;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 일주일 시간 가용성&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 평일 저녁: 30분 &lt;span class=&quot;token strike&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt; 1시간 (피곤할 때는 패스)
- 주말: 2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;~&lt;/span&gt;&lt;/span&gt;3시간 (둘째 주말은 대부분 외출)

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 이 책에 대한 기대&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 매주 1챕터씩, 부담 없이 따라할 수 있길
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 코드 같은 거 없이 대화창만으로 끝낼 수 있길
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 8주 후 정말 PDF가 손에 들리길&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Claude 프롬프트 — 페르소나 만들기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;내가 쓰려는 책의 한 줄 요약은 이거야:
&quot;[1단계에서 정한 한 줄 요약]&quot;

이 책의 첫 독자가 될 한 사람을 페르소나 카드 형태로 만들어줘.

조건:
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 가상의 인물이지만 구체적 (이름·나이·직업·거주지)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 글쓰기/AI 도구 경험 수준 명시
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; &quot;이 책을 왜 쓰고 싶은가&quot;의 동기 1&lt;span class=&quot;token strike&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;2개
- &quot;가장 큰 두려움&quot; 1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;~&lt;/span&gt;&lt;/span&gt;2개
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 일주일 시간 가용성 (평일/주말 각각)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 이 책에 거는 기대 3가지

출력은 위 항목을 갖춘 마크다운 페르소나 카드.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;받은 결과를 &lt;code class=&quot;language-text&quot;&gt;persona.md&lt;/code&gt;에 그대로 저장합니다. 이후 모든 챕터를 쓸 때 &lt;strong&gt;&quot;이 문장이 이 사람에게 닿는가?&quot;&lt;/strong&gt; 를 자문하는 게 페르소나의 유일한 역할입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 페르소나는 &lt;strong&gt;현실의 특정 인물을 그대로 복사하지 마세요.&lt;/strong&gt; 가상의 합성 인물이 안전하고 솔직한 글을 만듭니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;3단계 — 약속 (Promise)&lt;/h2&gt;
&lt;h3&gt;약속이란?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;이 책을 다 읽은 독자가 이 한 가지는 반드시 손에 쥡니다.&quot;&lt;/strong&gt; 라는 한 문장입니다. 마케팅의 카피가 아니라 &lt;strong&gt;저자가 자신과 맺는 계약&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;이 약속이 흐릿하면 8주 챌린지 중간에 &quot;이 챕터를 굳이 써야 하나?&quot; 갈등이 생길 때 기준이 없습니다. 명확하면 — 약속에 기여하지 않는 챕터는 &lt;strong&gt;빼는 게 맞다&lt;/strong&gt;는 결정을 빠르게 내릴 수 있습니다.&lt;/p&gt;
&lt;h3&gt;약속의 좋은 예&lt;/h3&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;본 책「나만의 책만들기」&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8주 후 자기만의 50p 전자책 PDF&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상 예 — 운동 책&lt;/td&gt;
&lt;td&gt;12주 뒤 푸시업 0개 → 30개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상 예 — 요리 책&lt;/td&gt;
&lt;td&gt;4주 뒤 손님 4명을 30분 만에 차려낼 수 있는 코스 1세트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상 예 — 글쓰기 책&lt;/td&gt;
&lt;td&gt;한 달 뒤 매일 200자씩 멈추지 않고 쓰는 사람&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;공통점 — &lt;strong&gt;검증 가능합니다.&lt;/strong&gt; &quot;글을 잘 쓰게 됩니다&quot; 같은 모호한 표현이 아니라, 다 읽은 사람이 &lt;strong&gt;자기가 약속을 받았는지 안 받았는지 스스로 판단&lt;/strong&gt;할 수 있어야 합니다.&lt;/p&gt;
&lt;h3&gt;Claude 프롬프트 — 약속 다듬기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;내 책 정보:
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 한 줄 요약: &quot;[1단계 결과]&quot;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 페르소나 핵심: &quot;[2단계 페르소나의 핵심 동기·기대 3줄]&quot;

이 책의 &quot;약속(promise)&quot;을 만들어줘.

조건:
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 다 읽은 독자가 손에 쥐는 구체적 결과 1가지
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 검증 가능 (독자가 스스로 &quot;받았다/못 받았다&quot; 판단 가능)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 분량/기간이 들어가면 더 좋음
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 후보 3개를 강도 순으로 정렬, 각 후보의 검증 방법도 함께

출력은 후보 3개 + 추천(왜 그것을 추천하는지 이유).&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;후보 3개 중 하나를 골라(또는 다듬어서) &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt;에 &quot;약속&quot; 섹션으로 박습니다.&lt;/strong&gt; 이후 챕터 검토 시 항상 이 약속을 기준으로 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;종합 — 콘셉트 시트 한 페이지&lt;/h2&gt;
&lt;p&gt;세 단계가 끝나면 한 페이지짜리 &lt;strong&gt;콘셉트 시트&lt;/strong&gt;가 나옵니다. 이걸 &lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt; 최상단에 두세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; 책 콘셉트 시트&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 한 줄 요약&lt;/span&gt;
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; [1단계 결과 — 50자 이내]

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 페르소나&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 이름: [가명]
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 직업·나이: [예: 마케터, 34세]
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 핵심 동기: [한 줄]
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 가장 큰 두려움: [한 줄]
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 시간 가용성: [평일 N분 / 주말 N시간]

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 약속&lt;/span&gt;
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; [3단계 결과 — 검증 가능한 한 문장]

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 메타 노트&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 작성일: 2026-05-04
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 다음 검토: 4장(자료 모으기) 마치고 페르소나·약속 일치 점검&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;좋은 콘셉트 시트의 체크리스트&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 한 줄 요약을 처음 보는 사람에게 보여줘도 &quot;어, 그거 누가 읽으면 좋겠다&quot; 떠오르는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 페르소나가 가족·친구 중 한 명을 떠올리게 하는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 약속을 8주 후 스스로에게 &quot;지켰다/못 지켰다&quot; 판정할 수 있는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 세 줄을 합쳐서 한 친구에게 30초 안에 설명할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;체크가 다 붙으면 다음 주(#3 차례 짜기)로 넘어갈 준비가 끝났습니다. 하나라도 안 붙으면 — &lt;strong&gt;다음 주 글을 보지 말고&lt;/strong&gt; 그 한 항목을 다듬는 데 시간을 더 쓰세요. 콘셉트가 흔들리는 채로 차례를 짜면 무조건 다시 짭니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;함정 4가지 — 콘셉트 단계에서 흔히 빠지는 실수&lt;/h2&gt;
&lt;h3&gt;1. &quot;AI를 활용한 ○○&quot; 같은 메타 표현&lt;/h3&gt;
&lt;p&gt;&quot;AI를 활용한 글쓰기&quot;, &quot;AI로 만드는 ○○&quot; — 어디서나 들리는 표현이라 차별화가 안 됩니다. AI 도구는 &lt;strong&gt;수단&lt;/strong&gt;이지 &lt;strong&gt;주제&lt;/strong&gt;가 아닙니다. 약속 한 줄에서 &apos;AI&apos;라는 단어를 빼고도 매력이 남는지 확인하세요.&lt;/p&gt;
&lt;h3&gt;2. 페르소나를 &quot;본인 자신&quot;으로 정하기&lt;/h3&gt;
&lt;p&gt;&quot;내가 모르던 시절의 나를 위한 책&quot; — 흔한 실수입니다. 본인의 과거는 너무 가까워서 객관화가 어렵고, 결과적으로 &lt;strong&gt;나만 이해할 수 있는 책&lt;/strong&gt;이 됩니다. 가족·친구·동료 중 &lt;strong&gt;현재 그 상황에 있는 한 사람&lt;/strong&gt;을 떠올리는 편이 훨씬 강합니다.&lt;/p&gt;
&lt;h3&gt;3. 약속의 단위가 너무 큼&lt;/h3&gt;
&lt;p&gt;&quot;인생이 바뀝니다&quot;, &quot;전문가가 됩니다&quot; — 검증 불가능합니다. 8주가 끝나는 날 독자가 &lt;strong&gt;물리적으로 손에 들고 보일 수 있는 결과&lt;/strong&gt; 또는 &lt;strong&gt;그 자리에서 보여줄 수 있는 행동&lt;/strong&gt;이 좋은 약속입니다.&lt;/p&gt;
&lt;h3&gt;4. 콘셉트 시트를 다른 사람에게 안 보여주기&lt;/h3&gt;
&lt;p&gt;머릿속에서는 명확해도 글로 적으면 모호해집니다. &lt;strong&gt;콘셉트 시트를 한 사람에게 보내고 한 줄 반응을 받으세요.&lt;/strong&gt; 가장 빠른 검증입니다. 댓글로 받아도 좋습니다(이 글 아래에).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. 한 줄 요약이 도저히 안 나옵니다. 그냥 넘어가도 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;아니요. 여기서 막히는 건 작은 문제가 아니라 큰 신호입니다.&lt;/strong&gt; 두 가지 가능성이 큽니다 — (1) 주제 자체가 너무 광범위함, 또는 (2) 본인이 정말 쓰고 싶은 게 무엇인지 아직 본인도 모름. 어느 쪽이든 &lt;strong&gt;한 주 더 쓰는 게 8주 전체를 살립니다.&lt;/strong&gt; Claude에게 &quot;내가 떠올린 답변에서 모순되는 지점을 짚어줘&quot;라고 부탁해보세요.&lt;/p&gt;
&lt;h3&gt;Q2. 페르소나를 여러 명 만들어도 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;가능하지만 추천하지 않습니다.&lt;/strong&gt; 페르소나는 의사결정 도구입니다. 둘 이상이 되면 갈등하는 순간 결정이 안 납니다. &lt;strong&gt;주 페르소나 1명 + 부 페르소나 1명(선택)&lt;/strong&gt; 이 최대치입니다.&lt;/p&gt;
&lt;h3&gt;Q3. 약속을 너무 좁게 잡으면 책이 작아 보이지 않을까요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;오히려 반대입니다.&lt;/strong&gt; 좁은 약속이 진짜 가치를 줍니다. &quot;글쓰기 잘하기&quot;보다 &quot;30일 동안 매일 200자씩 멈추지 않고 쓰기&quot;가 훨씬 강한 책입니다. 좁힌 약속이 실현되면 독자는 &lt;strong&gt;그 너머도 알아서 갑니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Q4. 한 줄 요약이 다른 책과 비슷해 보이는데요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비슷해도 괜찮습니다.&lt;/strong&gt; 비슷한 책이 있다는 건 시장이 있다는 뜻입니다. 차별화는 한 줄이 아니라 &lt;strong&gt;페르소나 + 약속 + 톤&lt;/strong&gt;의 조합에서 나옵니다. 한 줄을 억지로 비틀지 마세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;다음 주 예고 (#3 — 차례 짜기)&lt;/h2&gt;
&lt;p&gt;다음 주는 오늘 잡은 &lt;strong&gt;콘셉트 시트&lt;/strong&gt;를 들고 &lt;strong&gt;차례(outline)&lt;/strong&gt; 를 짭니다. outline은 책의 진실의 원천입니다 — 챕터 추가/삭제는 항상 outline부터, 그 다음 파일 작업으로 갑니다. Claude로 outline 후보를 여러 개 받아 비교하는 워크플로우와, 분량·페이스 검증 방법을 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘 세 가지만 끝내시면 됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Claude와 함께 &lt;strong&gt;한 줄 요약&lt;/strong&gt; 후보 5개 → 1개 확정 → &lt;code class=&quot;language-text&quot;&gt;README.md&lt;/code&gt; 최상단&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;페르소나 카드&lt;/strong&gt; 작성 → &lt;code class=&quot;language-text&quot;&gt;research/notes/persona.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;약속&lt;/strong&gt; 한 문장 → &lt;code class=&quot;language-text&quot;&gt;README.md&lt;/code&gt;에 박기&lt;/li&gt;
&lt;li&gt;✅ (보너스) 콘셉트 시트를 한 사람에게 보내 한 줄 반응 받기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;세 줄이 단단해졌다면 8주의 가장 어려운 고비를 넘긴 셈입니다. 나머지는 &lt;strong&gt;흐름을 따라가는&lt;/strong&gt; 일이 됩니다.&lt;/p&gt;
&lt;p&gt;여러분의 책의 &lt;strong&gt;한 줄 요약&lt;/strong&gt;은 무엇인가요? &lt;strong&gt;댓글로 한 줄만 남겨주시면&lt;/strong&gt;, 다음 주 #3에서 익명으로 인용하며 차례 짜기 사례로 함께 다뤄볼 수 있습니다. 한 줄을 글로 적어보는 것 자체가 이번 주의 가장 큰 진전입니다.&lt;/p&gt;
&lt;p&gt;다음 주에 또 뵙겠습니다. 자바파커였습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;시리즈 글 (Claude 책 만들기)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/book-making/book-making-01-getting-started/&quot;&gt;#1 시작 전 준비 — 도구와 마음가짐&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#2 콘셉트 잡기 — 누가 읽을지부터 정한다 (이번 글)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;#3 차례 짜기 — outline이 진실의 원천 (다음 주)&lt;/li&gt;
&lt;li&gt;#4 자료 모으기 (W20)&lt;/li&gt;
&lt;li&gt;#5 챕터 쓰기 (W21)&lt;/li&gt;
&lt;li&gt;#6 다듬기와 디자인 (W22)&lt;/li&gt;
&lt;li&gt;#7 빌드와 배포 (W23)&lt;/li&gt;
&lt;li&gt;#8 마무리와 부록 (W24)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Claude Cowork vs Claude Code — Anthropic 두 AI 에이전트 완벽 비교]]></title><description><![CDATA[Claude Cowork vs Claude Code 비교 — Anthropic의 업무용·코딩용 두 AI 에이전트 차이 정리 커버 안녕하세요, 자바파커입니다. "Claude Code는 들어봤는데, Cowork…]]></description><link>https://blog.javapark.kr/cowork-vs-claude-code/</link><guid isPermaLink="false">https://blog.javapark.kr/cowork-vs-claude-code/</guid><pubDate>Mon, 04 May 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/cowork-vs-claude-code.png&quot; alt=&quot;Claude Cowork vs Claude Code 비교 — Anthropic의 업무용·코딩용 두 AI 에이전트 차이 정리 커버&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Claude Code는 들어봤는데, Cowork는 또 뭐죠? 둘이 뭐가 다른가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;요즘 가장 많이 받는 질문입니다. 솔직히 처음 Cowork 발표가 났을 때 저도 &quot;이거 Claude Code의 비개발자 버전인가?&quot; 싶었습니다. 며칠 둘 다 써보고 나서야 두 도구가 &lt;strong&gt;같은 엔진을 공유하지만 완전히 다른 자리&lt;/strong&gt;를 노리고 있다는 게 보였습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Claude Code는 &quot;내 코드베이스에서 일하는 AI 동료&quot;&lt;/strong&gt;, &lt;strong&gt;Claude Cowork는 &quot;내 PC에서 문서·파일 업무를 대신하는 AI 동료&quot;&lt;/strong&gt; 입니다. 같은 Claude가 두 옷을 입고 있는 셈입니다. 오늘은 둘의 정체부터 사용 기준, 함께 쓰는 법까지 한 번에 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;한눈에 보는 차이 — TL;DR&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Claude Cowork&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;타깃&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;비개발자 지식근로자&lt;/td&gt;
&lt;td&gt;개발자·엔지니어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인터페이스&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;데스크톱 앱 (채팅 UI)&lt;/td&gt;
&lt;td&gt;터미널 / IDE 확장 (VS Code, JetBrains)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;주 작업&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;문서·파일·이메일·리서치&lt;/td&gt;
&lt;td&gt;코드베이스 읽기/수정/실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;출력물&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;정리된 폴더, 보고서, 요약, 데이터&lt;/td&gt;
&lt;td&gt;커밋, PR, 패치, 동작하는 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;러닝커브&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;낮음 (자연어 + 클릭)&lt;/td&gt;
&lt;td&gt;중간 (CLI·git·테스트 흐름 이해 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;요금&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;모든 유료 플랜 포함&lt;/td&gt;
&lt;td&gt;Pro/Max 또는 API 종량제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;플랫폼&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Windows, macOS 데스크톱&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux + IDE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Cowork — 비개발자를 위한 &quot;데스크톱 비서&quot;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/product/claude-cowork&quot;&gt;Claude Cowork&lt;/a&gt;는 Anthropic이 2026년 초 정식 출시한 &lt;strong&gt;지식근로자용 에이전틱 AI&lt;/strong&gt;입니다. 데스크톱에서 동작하면서 로컬 파일·폴더·앱을 직접 다룹니다.&lt;/p&gt;
&lt;h3&gt;누구를 위한 도구인가&lt;/h3&gt;
&lt;p&gt;Anthropic이 공식적으로 언급하는 타깃은 다음과 같습니다.&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;li&gt;&lt;strong&gt;마케팅·CS&lt;/strong&gt; — 초안 작성, 문의 응답 템플릿화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한 줄 요약하면 — &lt;strong&gt;코딩은 못 하지만 매일 문서·엑셀·메일과 씨름하는 모든 사람&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h3&gt;주요 기능&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;li&gt;&lt;strong&gt;데이터 추출&lt;/strong&gt; — 계약서·리포트 같은 비정형 파일에서 항목 뽑기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;플러그인 연동&lt;/strong&gt; — Google Drive, Gmail, DocuSign, FactSet 등 &lt;a href=&quot;https://techcrunch.com/2026/01/30/anthropic-brings-agentic-plugins-to-cowork/&quot;&gt;업무 도구와 연결&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;사용 방식&lt;/h3&gt;
&lt;p&gt;데스크톱 앱을 켜고 채팅창에 일감을 던지면 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;다운로드 폴더의 PDF를 회사별로 나눠서 정리하고, 2026년 분기별 매출 표가 있는 파일만 따로 빼서 요약해줘.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;터미널도, 코드도 필요 없습니다. 권한을 준 폴더 안에서 Claude가 &lt;strong&gt;읽고, 수정하고, 만들고, 결과를 보고&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code — 개발자를 위한 &quot;코드베이스 동료&quot;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.claude.com/en/docs/claude-code&quot;&gt;Claude Code&lt;/a&gt;는 &lt;strong&gt;터미널·IDE에서 동작하는 코딩 에이전트&lt;/strong&gt;입니다. 개인 포털 운영부터 서비스 개발까지, 저도 이 블로그 글 자체를 Claude Code로 쓰고 있습니다.&lt;/p&gt;
&lt;h3&gt;누구를 위한 도구인가&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;DevOps·SRE&lt;/strong&gt; — 스크립트 작성, 인프라 설정, 로그 분석&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테크 라이터&lt;/strong&gt; — 마크다운 기반 문서·블로그 운영&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;주요 기능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 전체 이해&lt;/strong&gt; — 최대 1M 토큰 컨텍스트로 코드베이스 통째로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파일 직접 편집&lt;/strong&gt; — Read/Edit/Write/Glob/Grep 등 코드 친화적 도구&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명령 실행&lt;/strong&gt; — &lt;code class=&quot;language-text&quot;&gt;npm test&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;, 빌드 명령을 Claude가 직접 호출&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 확장&lt;/strong&gt; — Playwright, GitHub, Slack 등 &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;MCP 서버&lt;/a&gt;로 외부 시스템 연동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VS Code / JetBrains 확장&lt;/strong&gt; — IDE 안에서 인라인으로 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서브에이전트·스킬·훅&lt;/strong&gt; — 작업 자동화의 거의 모든 레이어&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;사용 방식&lt;/h3&gt;
&lt;p&gt;터미널에서 &lt;code class=&quot;language-text&quot;&gt;claude&lt;/code&gt;를 치고 자연어로 지시하면, Claude가 프로젝트를 스캔해서 &lt;strong&gt;PR을 띄우거나 커밋을 만듭니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 예: 버그 리포트만 던져도 끝까지 처리&lt;/span&gt;
$ claude &lt;span class=&quot;token string&quot;&gt;&quot;랜딩 페이지 모바일에서 hero 배경이 깨지는 버그 잡고, 회귀 테스트도 추가해줘&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;핵심 차이 — &quot;출력물의 형태&quot;가 다르다&lt;/h2&gt;
&lt;p&gt;비교표는 위에 정리했지만, 실전에서 가장 큰 차이는 &lt;strong&gt;무엇이 결과물로 나오느냐&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;입력 (사용자 요청)&lt;/th&gt;
&lt;th&gt;Cowork 결과&lt;/th&gt;
&lt;th&gt;Claude Code 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&quot;월별 매출 정리해줘&quot;&lt;/td&gt;
&lt;td&gt;깔끔한 엑셀·보고서&lt;/td&gt;
&lt;td&gt;매출 집계 스크립트·대시보드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;이 계약서들 핵심만 뽑아줘&quot;&lt;/td&gt;
&lt;td&gt;표·요약 문서&lt;/td&gt;
&lt;td&gt;추출 자동화 파이프라인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;신제품 소개 글 초안 잡아줘&quot;&lt;/td&gt;
&lt;td&gt;Google Docs 초안&lt;/td&gt;
&lt;td&gt;블로그 정적 사이트 + 글 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;이메일 자동 분류 해줘&quot;&lt;/td&gt;
&lt;td&gt;Gmail 라벨·필터 설정&lt;/td&gt;
&lt;td&gt;Gmail API 호출 스크립트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;같은 문제도 &lt;strong&gt;Cowork는 &quot;결과물&quot;을, Claude Code는 &quot;결과물을 만드는 시스템&quot;을&lt;/strong&gt; 만듭니다. 일회성 업무는 Cowork가, 반복·확장이 필요한 업무는 Claude Code가 강합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;언제 무엇을 써야 할까 — 4가지 판단 기준&lt;/h2&gt;
&lt;h3&gt;1. &quot;코드를 남길 것인가?&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;남긴다&lt;/strong&gt; → Claude Code (변경이 git으로 추적되고 재현 가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안 남긴다&lt;/strong&gt; → Cowork (1회성 정리·요약·작성)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &quot;결과물을 다른 사람에게 넘길 것인가, 내가 쓸 것인가?&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;다른 사람에게 산출물(보고서·문서)을 전달&lt;/strong&gt; → Cowork&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다른 개발자도 같은 코드를 돌리고 고쳐야 함&lt;/strong&gt; → Claude Code&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. &quot;터미널·git이 익숙한가?&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;YES&lt;/strong&gt; → Claude Code의 자율성·강력함을 100% 활용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NO&lt;/strong&gt; → Cowork. 진입 장벽 없이 채팅만으로 끝&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. &quot;외부 SaaS와 직접 연결해야 하는가?&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Drive / Gmail / DocuSign 등 업무 SaaS&lt;/strong&gt; → Cowork 플러그인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub / 빌드 시스템 / 데이터베이스 / 사내 API&lt;/strong&gt; → Claude Code + MCP&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;함께 쓰는 법 — 1+1 시나리오&lt;/h2&gt;
&lt;p&gt;둘 중 하나만 쓸 필요는 없습니다. 같은 Anthropic 계정의 같은 유료 플랜에 둘 다 포함되니, &lt;strong&gt;역할 분담&lt;/strong&gt;이 정답에 가깝습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;시나리오 A — 콘텐츠 크리에이터&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cowork&lt;/strong&gt;: 자료 조사, 인터뷰 녹취록 정리, 썸네일용 이미지 후보 정리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;: 블로그 사이트 코드, 자동 발행 스크립트, SEO 메타 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;시나리오 B — 1인 사업가&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cowork&lt;/strong&gt;: 견적서·계약서 초안, 거래처별 자료 정리, 메일 응답 초안&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;: 제품 사이트, 결제 연동, 운영 자동화 스크립트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;시나리오 C — 개발자 본업 + 사이드&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cowork&lt;/strong&gt;: 회사 자료 요약, 회의록 정리, 분기 리포트 초안&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;: 본업 코드 리뷰·리팩터링, 사이드 프로젝트 전체&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심은 — &lt;strong&gt;&quot;문서·파일은 Cowork, 코드·시스템은 Claude Code&quot;&lt;/strong&gt; 라는 단순한 원칙으로 나누는 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q1. Cowork는 Claude Code의 상위 호환인가요?&lt;/strong&gt;
아닙니다. &lt;strong&gt;다른 영역&lt;/strong&gt;입니다. Cowork는 데스크톱 채팅 UI에서 문서·파일 업무에 최적화돼 있고, Claude Code는 터미널·IDE에서 코드베이스 작업에 최적화돼 있습니다. 같은 모델이라도 도구 셋·UI·실행 환경이 다릅니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q2. 둘 다 쓰려면 추가 결제가 필요한가요?&lt;/strong&gt;
Cowork는 &lt;a href=&quot;https://www.anthropic.com/pricing&quot;&gt;모든 유료 플랜&lt;/a&gt;에 기본 포함되어 별도 구매가 필요 없습니다. Claude Code는 동일 구독 안에서 사용량이 합산되며, API 종량제로도 쓸 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q3. Cowork도 명령 실행이 가능한가요?&lt;/strong&gt;
네, 권한을 준 폴더와 연결된 앱 범위 안에서 파일 생성·수정, 외부 SaaS API 호출 등이 가능합니다. 다만 &lt;strong&gt;터미널 명령 자유 실행은 Claude Code의 영역&lt;/strong&gt;이며, Cowork는 보다 안전한 사용자 친화적 액션 셋으로 제한되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q4. 비개발자인데 Claude Code를 쓰는 게 이득인 경우가 있을까요?&lt;/strong&gt;
있습니다. &lt;strong&gt;반복 업무를 자동화하고 싶은데 매번 손으로 하기 싫을 때&lt;/strong&gt;가 그렇습니다. 예: 매주 똑같은 파일 정리·발송. Cowork로 한 번 시킬 수 있지만, Claude Code로 스크립트 한 번 만들어 두면 다음부터는 클릭 한 번이면 끝입니다. 이 블로그의 &lt;a href=&quot;/claude-code/beginner-guide-2026/&quot;&gt;Claude Code 입문 가이드 2026&lt;/a&gt;에서 비개발자가 시작하는 방법도 정리해뒀습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Anthropic의 두 에이전트는 &lt;strong&gt;&quot;AI가 일을 한다&quot;&lt;/strong&gt; 는 같은 약속을 두 청중에게 다르게 전달합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;키보드 위에 손을 얹지 않고도 일이 굴러갔으면 좋겠다 → &lt;strong&gt;Cowork&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;키보드 위에서 더 빨리, 더 멀리 가고 싶다 → &lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저는 둘 다 쓰고 있습니다. 회사 자료·블로그 외 잡무는 Cowork가 처리하고, 블로그 사이트·자동화 스크립트·이 글의 초안 다듬기는 Claude Code가 처리합니다. &lt;strong&gt;시간을 어디에 쓰는가가 바뀌면, 만들 수 있는 결과물의 종류도 바뀝니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여러분은 둘 중 어느 쪽이 먼저 끌리시나요? 혹은 이미 두 도구를 함께 쓰고 계신다면 어떤 분담이 가장 잘 맞으셨는지, 댓글로 공유해주시면 다음 글에 반영하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;참고 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/product/claude-cowork&quot;&gt;Claude Cowork 공식 페이지 — Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://claude.com/product/cowork&quot;&gt;Cowork: Claude Code power for knowledge work — Claude.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.claude.com/en/docs/claude-code&quot;&gt;Claude Code 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://techcrunch.com/2026/01/30/anthropic-brings-agentic-plugins-to-cowork/&quot;&gt;Anthropic brings agentic plug-ins to Cowork — TechCrunch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;블로그 내 관련 글: &lt;a href=&quot;/claude-code/beginner-guide-2026/&quot;&gt;Claude Code 입문 가이드 2026&lt;/a&gt;, &lt;a href=&quot;/claude-code/token-saving-beginner/&quot;&gt;Claude Code 토큰 절약 — 입문편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Claude 책 만들기 #1 — 시작 전 준비: 도구 4가지와 첫 폴더 만들기]]></title><description><![CDATA[Claude 책 만들기 #1 — 시작 전 준비: 도구 4가지와 첫 폴더 만들기 "책 한 권 정도는 쓰고 싶은데, 시간이 없어서요." 가장 많이 듣는 말입니다. 솔직히 저도 그랬고요. 그런데 AI…]]></description><link>https://blog.javapark.kr/book-making-01-getting-started/</link><guid isPermaLink="false">https://blog.javapark.kr/book-making-01-getting-started/</guid><pubDate>Sun, 26 Apr 2026 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/book-making-01-getting-started.png&quot; alt=&quot;Claude 책 만들기 #1 — 시작 전 준비: 도구 4가지와 첫 폴더 만들기&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;책 한 권 정도는 쓰고 싶은데, 시간이 없어서요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;가장 많이 듣는 말입니다. 솔직히 저도 그랬고요. 그런데 AI 도구가 글쓰기·구조 설계·디자인·빌드까지 함께 거들어주는 지금은, &lt;strong&gt;8주에 한 챕터씩&lt;/strong&gt;만 쌓으면 한 권이 만들어집니다.&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다. 오늘부터 새 시리즈를 시작합니다 — &lt;strong&gt;Claude 책 만들기&lt;/strong&gt;. 8주에 걸쳐 전자책 「&lt;strong&gt;나만의 책만들기&lt;/strong&gt;」를 &lt;strong&gt;빌드 인 퍼블릭&lt;/strong&gt; 방식으로 함께 만듭니다. 더 강력한 점은 &lt;strong&gt;이 책 자체가 자기 제작 과정을 보여주는 책&lt;/strong&gt;이라는 것입니다. 즉 지금 보고 계신 이 글 자체가 그 책의 &lt;strong&gt;1장 초안&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;1장의 목표는 &quot;오늘 폴더 하나를 만들고, Claude와 첫 대화를 끝내는 것&quot;&lt;/strong&gt; 입니다. 8주 챌린지의 출발선이자, 가장 작은 한 걸음입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;누구나 책을 쓸 수 있는 시대&lt;/h2&gt;
&lt;p&gt;5년 전이라면 책 한 권을 만든다는 건 적어도 다음을 의미했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;50,000자 이상의 원고를 처음부터 끝까지 직접 쓰기&lt;/li&gt;
&lt;li&gt;출판사 검토 → 편집 → 디자인 → 인쇄 → 유통 (수개월~수년)&lt;/li&gt;
&lt;li&gt;표지·내지·일러스트 외주 또는 본인이 직접&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;지금은 어떤가요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;글쓰기 보조&lt;/strong&gt; — Claude가 초안·문체 정리·논리 검토를 동시에&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;구조 설계&lt;/strong&gt; — outline·차례·페르소나를 대화 한 번으로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디자인&lt;/strong&gt; — HTML+CSS로 표지·다이어그램을 30분 내 제작&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빌드&lt;/strong&gt; — &lt;code class=&quot;language-text&quot;&gt;pandoc&lt;/code&gt; 한 줄로 마크다운 → PDF/EPUB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유통&lt;/strong&gt; — 무료 PDF 호스팅, KDP, 리디 셀프 출판이 누구나 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여전히 &lt;strong&gt;글의 본질(독자에게 무엇을 전할 것인가)&lt;/strong&gt; 은 사람의 몫이지만, &lt;strong&gt;그 외 모든 마찰 비용&lt;/strong&gt;이 사라졌습니다. 이 시리즈가 보여드릴 것은 그 흐름을 어떻게 한 사람이 8주 안에 끝낼 수 있는가입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;우리가 만들 책 — 「나만의 책만들기」&lt;/h2&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;제목&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;나만의 책만들기&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부제 가안&lt;/td&gt;
&lt;td&gt;&quot;Claude로 8주 안에 전자책 만들기&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분량&lt;/td&gt;
&lt;td&gt;약 50p (전자책)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대상 독자&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;혼합&lt;/strong&gt; — 글쓰기·출판이 처음인 누구나 + Claude를 더 잘 쓰고 싶은 개발자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;출간 채널&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;무료 PDF 배포&lt;/strong&gt; (1차)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메타 컨셉&lt;/td&gt;
&lt;td&gt;&quot;이 책 자체가 자기 제작 과정을 보여주는 책&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;읽는 분이 따라하면 &lt;strong&gt;자기만의 50p 전자책 한 권&lt;/strong&gt;을 손에 쥐게 됩니다. 8주 시리즈가 끝나는 시점에 이 책의 PDF도 무료로 배포할 예정입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;8주 챌린지 한눈에&lt;/h2&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;&lt;strong&gt;W17 (오늘)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;#1 시작 전 준비&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;도구·폴더·마음가짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W18&lt;/td&gt;
&lt;td&gt;#2 콘셉트 잡기&lt;/td&gt;
&lt;td&gt;한 줄 요약·페르소나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W19&lt;/td&gt;
&lt;td&gt;#3 차례 짜기&lt;/td&gt;
&lt;td&gt;outline = 진실의 원천&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W20&lt;/td&gt;
&lt;td&gt;#4 자료 모으기&lt;/td&gt;
&lt;td&gt;블로그·노트 → 책 자료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W21&lt;/td&gt;
&lt;td&gt;#5 챕터 쓰기&lt;/td&gt;
&lt;td&gt;빌드 인 퍼블릭의 힘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W22&lt;/td&gt;
&lt;td&gt;#6 다듬기·디자인&lt;/td&gt;
&lt;td&gt;표지·다이어그램·문체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W23&lt;/td&gt;
&lt;td&gt;#7 빌드·배포&lt;/td&gt;
&lt;td&gt;pandoc → 무료 PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W24&lt;/td&gt;
&lt;td&gt;#8 마무리·부록&lt;/td&gt;
&lt;td&gt;회고·다음 책&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;W25부터는 8주간 발행한 챕터를 &lt;strong&gt;책 톤으로 정제&lt;/strong&gt;하고 PDF로 묶는 작업이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;필요한 도구 4가지&lt;/h2&gt;
&lt;p&gt;가장 자주 받는 질문 — &lt;strong&gt;&quot;무엇부터 깔아야 하나요?&quot;&lt;/strong&gt; — 에 답합니다. 결론부터 말씀드리면 &lt;strong&gt;딱 4가지&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h3&gt;1. Claude — 글의 동료&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://claude.ai&quot;&gt;claude.ai&lt;/a&gt;에 접속해서 무료 계정만 만들어도 충분합니다. 이 책 시리즈에서 Claude는 &lt;strong&gt;편집자·기획자·디자인 자문&lt;/strong&gt; 역할을 합니다. 비개발자도 대화창 하나로 모든 작업을 합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 글의 모든 결정(콘셉트·차례·표지 톤)은 사람이 내립니다. Claude는 옵션을 빠르게 제시하고 일관성을 점검하는 도우미입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. Claude Code — 코드·자동화의 동료 (개발자용 사이드)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;비개발자라면 이 섹션은 건너뛰셔도 됩니다.&lt;/strong&gt; 본문 전체는 Claude(웹)만으로 따라올 수 있게 구성됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.claude.com/claude-code/&quot;&gt;Claude Code&lt;/a&gt;는 터미널에서 동작하는 Claude입니다. 책 작업에서는 &lt;strong&gt;자동화&lt;/strong&gt; 영역을 담당합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;블로그 글을 책 발췌(&lt;code class=&quot;language-text&quot;&gt;drafts/from-blog/&lt;/code&gt;)로 자동 미러&lt;/li&gt;
&lt;li&gt;표지 HTML을 한 번에 PNG로 캡처(Playwright 연동)&lt;/li&gt;
&lt;li&gt;pandoc 빌드 명령을 wrapping&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 마크다운 — 글의 형식&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.md&lt;/code&gt; 확장자의 일반 텍스트 파일입니다. 무거운 워드 프로세서 대신 &lt;strong&gt;텍스트 + 약간의 기호&lt;/strong&gt;로 글을 씁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; 제목&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 소제목&lt;/span&gt;

&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;굵게&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;token italic&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;기울임&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;링크&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://...&lt;/span&gt;)&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 목록 1
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 목록 2

&lt;span class=&quot;token blockquote punctuation&quot;&gt;&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;4. pandoc — 빌드 도구&lt;/h3&gt;
&lt;p&gt;마크다운을 &lt;strong&gt;PDF·EPUB·HTML로 한 번에 변환&lt;/strong&gt;해주는 도구입니다. 7장에서 본격적으로 다룹니다. 지금은 &quot;이런 게 있다&quot; 정도만 기억하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실습 1 — 작업 폴더 만들기&lt;/h2&gt;
&lt;p&gt;이 책 작업을 위한 디렉토리 구조입니다. &lt;strong&gt;오늘 할 일은 이 폴더를 그대로 만드는 것&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;book/
├── README.md         # 책 메타 정보
├── outline.md        # 전체 차례 (진실의 원천)
├── chapters/         # 정제된 챕터
├── drafts/
│   ├── from-blog/    # 블로그 글 발췌
│   └── reworked/     # 책 톤 재구성 작업장
├── research/
│   ├── notes/        # 자유 메모
│   ├── case-studies/ # 실전 케이스
│   └── references.md # 참고 자료
├── assets/
│   ├── images/       # 표지·다이어그램 PNG
│   └── diagrams/     # HTML 원본
├── build/            # PDF·EPUB 산출물
└── scripts/          # 자동화 스크립트 (개발자만)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;비개발자 — 파일 탐색기로&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;원하는 위치에 &lt;code class=&quot;language-text&quot;&gt;book&lt;/code&gt; 폴더 생성&lt;/li&gt;
&lt;li&gt;그 안에 위 구조대로 하위 폴더들을 손으로 만들기&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt;를 메모장(혹은 Notion·VS Code)으로 새로 만들고 책 제목 한 줄만 적어두기&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;개발자 — 명령어 한 줄로&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; book/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;chapters,drafts/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;from-blog,reworked&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,research/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;notes,case-studies&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,assets/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;images,diagrams&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,build,scripts&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;오늘은 폴더만 비어 있으면 됩니다. &lt;strong&gt;빈 그릇을 먼저 놓는 것&lt;/strong&gt;이 1장의 핵심입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실습 2 — Claude와 첫 대화: 책 콘셉트 인터뷰&lt;/h2&gt;
&lt;p&gt;폴더가 만들어지면, Claude를 열고 &lt;strong&gt;딱 5개 질문&lt;/strong&gt;을 받아봅니다. 다음 프롬프트를 그대로 복사해서 붙여 넣어보세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;나는 「나만의 책만들기」라는 전자책을 만들고 싶다. 8주에 걸쳐 한 챕터씩 발행하는 빌드 인 퍼블릭 방식이다.

다음 5가지 질문에 차례로 답할 수 있게 하나씩 물어봐 줘.
질문 하나에 답하면 다음 질문으로 넘어가는 식으로.

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; 이 책을 한 줄로 요약하면?
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; 누구를 위한 책인가? (구체적인 한 사람)
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; 이 책을 다 읽은 독자가 얻게 되는 것 한 가지는?
&lt;span class=&quot;token list punctuation&quot;&gt;4.&lt;/span&gt; 비슷한 책과 어떻게 다른가?
&lt;span class=&quot;token list punctuation&quot;&gt;5.&lt;/span&gt; 8주가 끝나는 시점에 어떤 모습이 되고 싶은가?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude가 한 번에 한 질문씩 던집니다. &lt;strong&gt;5개 답변을 다 마치는 것&lt;/strong&gt;이 오늘의 두 번째이자 마지막 미션입니다. 답변 자체는 길게 쓸 필요 없고, &lt;strong&gt;떠오르는 그대로&lt;/strong&gt; 적어두세요.&lt;/p&gt;
&lt;p&gt;답변이 끝나면 Claude에게 마지막으로 부탁하세요.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;방금 5개 답변을 책 README의 첫 단락 형태로 정리해줘.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;받은 결과를 &lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt;에 그대로 복사해두면, 다음 주 #2(콘셉트 잡기)의 출발점이 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마음가짐 — 완벽보다 발행&lt;/h2&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;/li&gt;
&lt;li&gt;✅ 부족한 부분은 댓글·피드백으로 다듬기&lt;/li&gt;
&lt;li&gt;✅ 8주 후 정제 단계에서 책 톤으로 재구성&lt;/li&gt;
&lt;li&gt;❌ &quot;완성 후 한 번에 공개&quot;는 시도하지 않습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;매주 발행 약속이 챌린지의 거의 전부입니다. 글의 완성도는 8주 동안 자연스럽게 따라옵니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;다음 주 예고 (#2 — 콘셉트 잡기)&lt;/h2&gt;
&lt;p&gt;다음 주는 오늘 Claude와 나눈 &lt;strong&gt;5가지 답변&lt;/strong&gt;을 기반으로 &lt;strong&gt;책의 한 줄 요약·페르소나·약속(promise)&lt;/strong&gt; 을 단단히 잡습니다. 이 단계가 흔들리면 8주가 흔들리니, 가장 공들여 다룰 챕터입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. 비개발자도 정말 따라할 수 있나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;가능합니다.&lt;/strong&gt; 본문은 모두 &lt;a href=&quot;https://claude.ai&quot;&gt;claude.ai&lt;/a&gt; 대화창 하나로 진행할 수 있게 구성됩니다. Claude Code·pandoc·자동화 스크립트는 &quot;있으면 더 편하다&quot; 정도이고, 부록·사이드바로 분리됩니다.&lt;/p&gt;
&lt;h3&gt;Q2. 책의 저작권은 누구 것인가요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;전적으로 본인&lt;/strong&gt;입니다. Claude는 도구이고, 글의 결정은 모두 사람이 내립니다. 다만 출간 시 &quot;AI 도구 활용 명시&quot;를 권장합니다(8장에서 다룸).&lt;/p&gt;
&lt;h3&gt;Q3. 8주 안에 진짜 만들어지나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;8주가 끝나는 시점에 50p 전자책 PDF가 손에 쥐어집니다.&lt;/strong&gt; 이 시리즈가 그 증거가 될 예정입니다(제 경우).&lt;/p&gt;
&lt;h3&gt;Q4. 이미 다른 글을 쓰고 있는데, 그걸 책으로 바꿀 수 있나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;가능합니다.&lt;/strong&gt; 4장(자료 모으기)에서 기존 블로그·노트를 책 자료로 가져오는 워크플로우를 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘 두 가지만 끝내시면 됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;code class=&quot;language-text&quot;&gt;book/&lt;/code&gt; 폴더 만들기 (구조만, 비어 있어도 OK)&lt;/li&gt;
&lt;li&gt;✅ Claude와 5개 질문 대화 → 결과를 &lt;code class=&quot;language-text&quot;&gt;book/README.md&lt;/code&gt;에 붙여넣기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음 주 #2에서 그 결과를 가지고 &lt;strong&gt;책의 콘셉트를 단단히 잡는 작업&lt;/strong&gt;을 시작합니다.&lt;/p&gt;
&lt;p&gt;여러분이 만들고 싶은 책의 한 줄 요약은 무엇인가요? &lt;strong&gt;댓글로 남겨주시면&lt;/strong&gt; 다음 주 글에서 함께 다뤄볼 사례로 인용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;다음 주에 또 뵙겠습니다. 자바파커였습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude로 QA 업무 자동화하기 — 테스트 케이스 생성부터 버그 분석까지]]></title><description><![CDATA[Claude로 QA 업무 자동화하기 — 테스트 케이스부터 버그 분석까지 실전 가이드 안녕하세요, 자바파커입니다. "QA 한 명이 일주일 동안 짜야 하는 테스트 케이스를, AI가 1시간 만에 초안을 만들어줍니다." 요즘 QA 팀에서 Claude…]]></description><link>https://blog.javapark.kr/qa-automation-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/qa-automation-guide/</guid><pubDate>Sat, 25 Apr 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-qa-automation-guide.png&quot; alt=&quot;Claude로 QA 업무 자동화하기 — 테스트 케이스부터 버그 분석까지 실전 가이드&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;QA 한 명이 일주일 동안 짜야 하는 테스트 케이스를, AI가 1시간 만에 초안을 만들어줍니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;요즘 QA 팀에서 Claude를 도입하기 시작한 회사들이 늘고 있습니다. 솔직히 말하면, 처음에는 저도 반신반의했습니다. &quot;AI가 도메인 모르고 어떻게 테스트를 짜?&quot;라는 의심부터 들었으니까요.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Claude는 QA 업무의 70%를 차지하는 반복 작업(테스트 케이스 작성, 회귀 시나리오 정리, 테스트 데이터 생성)을 빠르게 줄여줍니다.&lt;/strong&gt; 단, 도메인 지식이나 사용자 시나리오는 여전히 사람이 채워야 합니다.&lt;/p&gt;
&lt;p&gt;오늘은 실제 QA 업무에서 Claude를 어떻게 써먹을 수 있는지, &lt;strong&gt;7가지 실전 활용법&lt;/strong&gt;으로 정리해드립니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA에서 Claude가 잘하는 일 vs 못하는 일&lt;/h2&gt;
&lt;p&gt;먼저 큰 그림부터 잡고 갑시다. 모든 작업에 Claude가 만능은 아닙니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th&gt;Claude가 잘함&lt;/th&gt;
&lt;th&gt;Claude가 약함&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;테스트 케이스 작성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;명세 기반 케이스 초안 양산&lt;/td&gt;
&lt;td&gt;도메인 특수 케이스, 숨겨진 엣지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;테스트 코드 작성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Playwright/Jest/Selenium 스크립트&lt;/td&gt;
&lt;td&gt;복잡한 인증/세션 흐름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;버그 리포트 분석&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로그/스택트레이스 요약&lt;/td&gt;
&lt;td&gt;재현 불가 이슈의 근본 원인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;테스트 데이터 생성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;다양한 케이스의 더미 데이터&lt;/td&gt;
&lt;td&gt;실제 운영 데이터의 통계 분포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;회귀 테스트 시나리오&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;변경 영향도 기반 정리&lt;/td&gt;
&lt;td&gt;우선순위 판단(비즈니스 임팩트)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심 원칙은 이겁니다 — &lt;strong&gt;Claude는 &quot;초안 생성기&quot;로 쓰고, 검수와 도메인 보정은 사람이 한다.&lt;/strong&gt; 이 원칙만 지켜도 QA 생산성이 두 배 가까이 올라갑니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 1 — 명세 기반 테스트 케이스 자동 생성&lt;/h2&gt;
&lt;p&gt;가장 즉각적인 효과를 보는 영역입니다. PRD나 API 명세서를 던져주면 Claude가 테스트 케이스를 표 형태로 뽑아줍니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;아래 API 명세를 기준으로 테스트 케이스를 작성해주세요.
형식: 표 (TC ID, 시나리오, 사전 조건, 입력값, 예상 결과, 우선순위)

[API 명세]
POST /api/users/signup
- email (string, required, 이메일 형식)
- password (string, required, 8~32자, 영문/숫자/특수문자 포함)
- nickname (string, required, 2~16자)

성공: 201 Created, 사용자 ID 반환
실패: 400 (유효성), 409 (이메일 중복)

정상 케이스 / 경계값 / 예외 케이스를 나누어서 정리해주세요.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 던지면 보통 &lt;strong&gt;15~30개 케이스&lt;/strong&gt;가 한 번에 나옵니다. 경계값(8자 비밀번호, 32자 비밀번호, 7자/33자 등)이나 흔한 예외(공백 nickname, 특수문자 미포함 비밀번호)도 빠짐없이 챙깁니다.&lt;/p&gt;
&lt;h3&gt;한 단계 더 — Excel 형식으로 바로 변환&lt;/h3&gt;
&lt;p&gt;QA 도구가 엑셀 기반이라면 이렇게 추가 요청합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;위 케이스를 CSV 형식으로 변환해주세요. 헤더는 한글로 부탁드립니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;저장하면 바로 TestRail이나 Zephyr에 import 할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 2 — Playwright/Selenium 테스트 코드 작성&lt;/h2&gt;
&lt;p&gt;Claude Code 환경에서 특히 강력합니다. UI 시나리오를 자연어로 설명하면 Playwright 스크립트가 바로 나옵니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;다음 시나리오를 Playwright (TypeScript)로 작성해주세요.

1. https://example.com/login 접속
2. 이메일 input에 &quot;test@example.com&quot; 입력
3. 비밀번호 input에 &quot;wrong-password&quot; 입력
4. 로그인 버튼 클릭
5. &quot;.error-message&quot; 셀렉터에 &quot;비밀번호가 일치하지 않습니다&quot; 텍스트가 보이는지 확인
6. 5번 연속 실패 시 계정 잠금 메시지 노출 확인&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;생성된 코드는 거의 바로 돌아갑니다. 특히 &lt;strong&gt;셀렉터 추적, 대기 시간 처리(&lt;code class=&quot;language-text&quot;&gt;waitForSelector&lt;/code&gt;), 에러 핸들링&lt;/strong&gt; 같은 기본기는 Claude가 알아서 챙깁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@playwright/test&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;로그인 실패 시 에러 메시지 노출&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; page &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;goto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://example.com/login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;input[name=&quot;email&quot;]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;input[name=&quot;password&quot;]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;wrong-password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;button[type=&quot;submit&quot;]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.error-message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;비밀번호가 일치하지 않습니다&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Tip — MCP Playwright 활용&lt;/h3&gt;
&lt;p&gt;Claude Code에서 &lt;strong&gt;MCP Playwright 서버&lt;/strong&gt;를 연결해두면 코드 작성 → 실제 브라우저 실행 → 결과 확인까지 한 번에 됩니다. UI 회귀 테스트가 필요한 팀이라면 도입 가치가 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 3 — 버그 리포트 분석 및 재현 단계 정리&lt;/h2&gt;
&lt;p&gt;QA 업무 중 의외로 시간을 많이 잡아먹는 게 &lt;strong&gt;버그 리포트 정리&lt;/strong&gt;입니다. 사용자가 보낸 너저분한 메시지를 개발자가 볼 수 있는 형태로 다듬어야 하니까요.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;아래 사용자 문의를 표준 버그 리포트 형식으로 정리해주세요.
형식: 제목 / 환경 / 재현 단계 / 기대 결과 / 실제 결과 / 우선순위 / 첨부

[사용자 문의]
&quot;안녕하세요 어제부터 결제가 안돼요... 카드 정보 넣고 결제 누르면 빙글빙글 돌다가 그냥 처음 화면으로 돌아가요. 갤럭시 S22 쓰고 있고 앱 최신 버전이에요. 어제 오후 3시쯤부터 그래요.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude가 정리해주는 결과는 거의 그대로 Jira에 붙여 넣을 수 있는 수준입니다. &lt;strong&gt;재현 단계 누락, 환경 정보 부족, 우선순위 미지정&lt;/strong&gt; 같은 흔한 문제를 알아서 보강합니다.&lt;/p&gt;
&lt;h3&gt;로그 분석에도 강력함&lt;/h3&gt;
&lt;p&gt;스택트레이스나 서버 로그를 통째로 던져주고 &quot;이 에러의 원인이 뭘까요?&quot;라고 물으면 가능성 있는 원인을 우선순위대로 정리해줍니다. 특히 처음 보는 라이브러리 에러를 빠르게 이해할 때 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 4 — 테스트 데이터 생성&lt;/h2&gt;
&lt;p&gt;회원가입 50건, 상품 등록 100건, 주문 200건처럼 &lt;strong&gt;다양한 패턴의 더미 데이터&lt;/strong&gt;가 필요할 때 Claude가 빠릅니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;아래 스키마에 맞는 회원 더미 데이터를 JSON 형식으로 30건 생성해주세요.
- 정상 데이터 20건, 경계값 5건, 예외 케이스 5건
- 한국 이름과 이메일을 사용해주세요

스키마:
{
  &quot;name&quot;: &quot;string (2~10자)&quot;,
  &quot;email&quot;: &quot;string (이메일 형식)&quot;,
  &quot;age&quot;: &quot;number (14~99)&quot;,
  &quot;phone&quot;: &quot;string (010-XXXX-XXXX)&quot;,
  &quot;address&quot;: &quot;string&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;김민준&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;minjun.kim@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ... &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이서연&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;seoyeon.lee@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ... &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;장점은 &lt;strong&gt;데이터의 다양성&lt;/strong&gt;입니다. Faker 같은 라이브러리는 무작위 생성이지만, Claude는 &quot;경계값 5건&quot;이라고 하면 의미 있는 경계값(최소/최대 나이, 짧은/긴 이름 등)을 의도적으로 만들어줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 5 — 회귀 테스트 시나리오 설계&lt;/h2&gt;
&lt;p&gt;코드 변경이 발생했을 때 **어디까지 다시 테스트해야 하나?**를 결정하는 건 QA의 핵심 역량입니다. Claude를 잘 쓰면 변경 영향도 분석을 빠르게 할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;다음 코드 변경 사항을 기준으로 회귀 테스트가 필요한 영역을 정리해주세요.
- 변경 코드: src/auth/login.ts (로그인 토큰 만료 시간 1시간 → 24시간)
- 영향 받을 가능성이 있는 모듈을 추론하고, 회귀 테스트 항목을 정리해주세요.
- 우선순위(상/중/하)를 함께 표기해주세요.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude가 잡아주는 영역은 보통 이런 식입니다.&lt;/p&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;인증&lt;/td&gt;
&lt;td&gt;토큰 만료 후 재로그인, 자동 로그아웃 동작&lt;/td&gt;
&lt;td&gt;상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;세션 관리&lt;/td&gt;
&lt;td&gt;다중 디바이스 로그인 시 토큰 처리&lt;/td&gt;
&lt;td&gt;상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안&lt;/td&gt;
&lt;td&gt;만료 시간 늘어남에 따른 토큰 탈취 시나리오&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;토큰 검증 미들웨어가 있는 모든 엔드포인트&lt;/td&gt;
&lt;td&gt;상&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;물론 도메인 지식이 있는 QA가 보강해야 하지만, &lt;strong&gt;시작점&lt;/strong&gt;으로는 훌륭합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 6 — 코드 리뷰(QA 관점)&lt;/h2&gt;
&lt;p&gt;QA가 코드 리뷰에 참여하는 팀이라면, 리뷰 시간을 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;아래 PR 코드를 QA 관점에서 리뷰해주세요. 다음 항목을 체크해주세요.
1. 입력 유효성 검증이 빠진 곳
2. 에러 처리가 누락된 곳
3. 로깅이 부족한 곳
4. 테스트 코드 커버리지 보강이 필요한 부분
5. 잠재적 보안 이슈

[PR 코드]
... (코드 첨부)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;특히 &lt;strong&gt;로깅 누락&lt;/strong&gt;과 &lt;strong&gt;에러 메시지 사용자 노출 여부&lt;/strong&gt;를 잘 잡습니다. QA는 사용자 경험 관점에서 보지만, 개발자는 기술 관점에서 보기 때문에 이 차이가 자주 놓쳐지거든요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QA 활용법 7 — 테스트 결과 리포트 자동화&lt;/h2&gt;
&lt;p&gt;테스트 실행 결과를 정리해서 주간 리포트로 만드는 작업, 이거 의외로 시간 잡아먹습니다. Claude로 자동화할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;실전 프롬프트 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;다음 Playwright 테스트 결과 JSON을 분석해서 주간 QA 리포트를 작성해주세요.
- 전체 통과/실패 비율
- 주요 실패 원인 그룹화 (셀렉터 변경, API 응답 변경, 타임아웃 등)
- 다음 주 우선 점검 영역 추천

[결과 데이터]
... (JSON 첨부)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;매주 같은 형식의 리포트가 필요하다면, 이 프롬프트를 &lt;strong&gt;Claude Code Skill&lt;/strong&gt;로 만들어두면 명령어 한 번으로 리포트가 나옵니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전에서 주의해야 할 3가지&lt;/h2&gt;
&lt;p&gt;Claude를 QA에 도입할 때 빠지기 쉬운 함정도 정리해둡니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 명세를 그대로 믿지 말기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude는 명세에 적힌 그대로 케이스를 만듭니다. &lt;strong&gt;&quot;명세에 없지만 사용자가 할 만한 행동&quot;&lt;/strong&gt;(예: 폼 입력 중 뒤로가기, 더블 클릭, 네트워크 끊김)은 사람이 추가해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 도메인 데이터로 검증하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;생성된 더미 데이터를 그대로 운영 환경에 가까운 테스트에 쓰면 위험합니다. 통계 분포(예: 결제 금액의 80%가 1만원 미만)는 실제 데이터로 보정하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 보안 테스트는 직접 학습한 사람이 해야&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude가 OWASP Top 10 기반 케이스는 만들어주지만, &lt;strong&gt;실제 침투 테스트나 0-day 발견&lt;/strong&gt;은 보안 전문가의 영역입니다. AI에 맡기지 마세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. QA 신입에게도 Claude 도입을 추천하시나요?&lt;/strong&gt;
네, 오히려 신입에게 더 추천합니다. 시니어 QA의 사고 흐름을 빠르게 익힐 수 있는 도구가 됩니다. 단, &lt;strong&gt;Claude가 만든 케이스를 무조건 믿지 말고 검토하는 습관&lt;/strong&gt;을 처음부터 들여야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 어떤 모델을 쓰는 게 좋을까요?&lt;/strong&gt;
복잡한 케이스 분석은 &lt;strong&gt;Opus&lt;/strong&gt;, 빠른 더미 데이터 생성이나 단순 변환은 &lt;strong&gt;Haiku&lt;/strong&gt;를 권합니다. 비용/속도 균형은 &lt;strong&gt;Sonnet&lt;/strong&gt;이 좋습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 회사 코드를 Claude에 그대로 넣어도 되나요?&lt;/strong&gt;
보안 정책 확인이 우선입니다. 민감한 코드라면 &lt;strong&gt;Claude for Enterprise&lt;/strong&gt;(데이터 학습 미사용 보장)나 &lt;strong&gt;AWS Bedrock의 Claude&lt;/strong&gt;를 검토하세요. 개인 프로젝트는 일반 Claude로 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — Claude는 QA를 &quot;줄이지&quot; 않고 &quot;옮긴다&quot;&lt;/h2&gt;
&lt;p&gt;Claude를 도입한다고 QA 인력이 줄어드는 게 아닙니다. &lt;strong&gt;반복 작업이 줄어드는 만큼, 본질적인 QA 업무(사용자 시나리오 설계, 비즈니스 임팩트 평가, 도메인 특화 테스트)에 더 집중할 수 있게 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;저도 Claude 도입 전에는 테스트 케이스 작성에 일주일을 쓰고 정작 시나리오 검증에 이틀을 썼는데, 지금은 그 비중이 완전히 뒤집혔습니다. AI가 단순 작업을 가져가고, 사람은 판단에 집중하는 구조 — 이게 가장 건강한 QA 자동화 방향이라고 생각합니다.&lt;/p&gt;
&lt;p&gt;여러분은 QA 업무에서 Claude를 어떻게 활용하고 계신가요? 댓글로 경험을 공유해주시면, 다음 포스팅에서 실전 사례로 다뤄보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Google Search Console 인덱싱 자동 모니터링 — GitHub Actions + Telegram 30분 구축기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "블로그에 공들여 글 올렸는데 구글에 안 뜨고 있는 걸 몇 주 뒤에 알게 된 적, 있으신가요?" 솔직히 저도 그랬습니다. 포스팅만 올리고 Search Console…]]></description><link>https://blog.javapark.kr/gsc-indexing-monitor/</link><guid isPermaLink="false">https://blog.javapark.kr/gsc-indexing-monitor/</guid><pubDate>Wed, 22 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;블로그에 공들여 글 올렸는데 구글에 안 뜨고 있는 걸 몇 주 뒤에 알게 된 적, 있으신가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. 포스팅만 올리고 Search Console은 한 달에 한 번 열어볼까 말까 했고, &quot;알아서 크롤링되겠지&quot; 하고 방치했습니다. 그러다 최근에 모니터를 만들어 &lt;strong&gt;첫 실행&lt;/strong&gt;한 결과가 충격적이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;57개 URL 중 인덱싱된 건 0개. 전부 &quot;Google에는 아직 알려지지 않은 URL입니다&quot; 상태였습니다. 몇 달 동안 제가 올린 글이 검색에 뜨지 않고 있었던 겁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;GSC URL Inspection API + GitHub Actions cron + Telegram 알림을 엮으면, 이런 상황을 주간 리포트로 자동 포착할 수 있습니다.&lt;/strong&gt; Claude Code로 30분 안에 완성됐고, 덕분에 &quot;구글이 내 블로그를 모른다&quot;는 사실을 즉시 알아챘습니다. 오늘은 이 시스템의 전체 구조·셋업·실제 결과 분석·Claude Code 활용 회고까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;Claude Code 관련 내용이 궁금하시면 &lt;a href=&quot;/subagent-essential-guide/&quot;&gt;Claude Code Subagent 완벽 가이드&lt;/a&gt;도 같이 보시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 &quot;자동 모니터링&quot;인가 — 수동 GSC의 한계&lt;/h2&gt;
&lt;p&gt;Google Search Console을 &lt;strong&gt;수동으로&lt;/strong&gt; 여는 방식은 몇 가지 치명적 한계가 있습니다.&lt;/p&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;자발적 로그인이 필요&lt;/td&gt;
&lt;td&gt;바쁘면 2~3주씩 안 들어감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체 URL 일괄 조회가 불편&lt;/td&gt;
&lt;td&gt;보통 문제 있는 URL 하나 찍어서 확인, 나머지는 모른 채&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;알림 기능이 제한적&lt;/td&gt;
&lt;td&gt;이메일 알림은 큰 이슈만, &quot;천천히 밀린 인덱싱&quot;은 놓침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추이 추적이 어려움&lt;/td&gt;
&lt;td&gt;지난달 대비 인덱싱 비율 변화 같은 건 수동 기록해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 속성 관리가 번거로움&lt;/td&gt;
&lt;td&gt;블로그·쇼핑몰·랜딩 각각 따로 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;자동화의 핵심: &quot;없어도 되는 작업&quot;은 자동화하고, 사람은 결과만 확인&lt;/strong&gt;하는 흐름으로 전환.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;전체 아키텍처 — 주간 5단계 파이프라인&lt;/h2&gt;
&lt;p&gt;시스템의 흐름은 단순합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[GitHub Actions cron]
         ↓
  1. sitemap-index.xml fetch → URL 전체 추출
         ↓
  2. URL마다 GSC URL Inspection API 호출 (서비스 계정 인증)
         ↓
  3. 이전 상태(data/indexing-status.json)와 diff 계산
         ↓
  4. 변화가 있으면 Telegram HTML 메시지 전송
         ↓
  5. 새 상태를 JSON에 저장하고 자동 커밋&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;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/9a8bd225cdac57086b9e83c0a8c4db12/64756/diagram-pipeline.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 43.25581395348837%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAABvUlEQVR42g2RWW6jQABEuUHkiK27wawNvbCvzW5jjOOJM8lP5v5HGaSnUv08qaSS3vCkhJOXXoN81YJRC68mvgCnBl4F/Qbh1giEGXZn0pukPzrCAro1cGvdrSWleJ3FT77+lrd/qHjC7MtOXgZbIJsgmxG/mNHVTFYjvhrRBfIZ0AnEi85GzS4lkD6c9q/bf5+rT41fQbyi9GbkO0zvKNuMbEPZDrMdpRtINj1adX7R2l3JVtUqpFP6cUr29+iG8mdAXz59uOyD50vbZCxpqn5u8lvDNk6XMpqMalSqCfDeShb5nEuw/ITVl0wXnV54+sPTbxOvIR+6riHRQRWxYSxePJyxK8xitsZdpUIlrWzmEghHzanfUCbbjU1mKxgUVFpe7bPB9EpoRY5XZs0Ts1lHmREKi04nI3m3c/lcSDC+mummkEknE0mfYfywg9VnY9kuUSZ8mhEq4nj1PYGdDtLOa+5WfpFxI6NDTkZ33OG0Wf0WDA+yPOnyBw97ON9xf4NRC7gATOik1cNWPSBCCYXsH/IxG7fweI90Bh1MNpp8PPLoiPQg7AAWut9qXqO6teLUql2rVnOgO0I2iv/EC15Pxu7oogAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/9a8bd225cdac57086b9e83c0a8c4db12/a1b3b/diagram-pipeline.webp 215w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/fc700/diagram-pipeline.webp 430w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/521f7/diagram-pipeline.webp 860w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/cbd37/diagram-pipeline.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/9a8bd225cdac57086b9e83c0a8c4db12/f7886/diagram-pipeline.png 215w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/bc750/diagram-pipeline.png 430w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/3e3fe/diagram-pipeline.png 860w,
/static/9a8bd225cdac57086b9e83c0a8c4db12/64756/diagram-pipeline.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/9a8bd225cdac57086b9e83c0a8c4db12/3e3fe/diagram-pipeline.png&apos; alt=&apos;GSC 인덱싱 모니터 주간 5단계 파이프라인 — sitemap fetch → URL Inspection API → Diff → Telegram → 자동 커밋&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;GSC 인덱싱 모니터 주간 5단계 파이프라인 — sitemap fetch → URL Inspection API → Diff → Telegram → 자동 커밋&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;사전 준비 3종 — 총 20분&lt;/h2&gt;
&lt;h3&gt;1) GCP 서비스 계정 (15분)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://console.cloud.google.com&quot;&gt;Google Cloud Console&lt;/a&gt;에서 프로젝트 생성 (예: &lt;code class=&quot;language-text&quot;&gt;gsc-monitor&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;APIs &amp;#x26; Services → Library → &lt;em&gt;Google Search Console API&lt;/em&gt; 활성화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM &amp;#x26; Admin → Service Accounts&lt;/strong&gt; → 서비스 계정 생성 (역할 없이도 OK)&lt;/li&gt;
&lt;li&gt;서비스 계정 → &lt;strong&gt;Keys&lt;/strong&gt; 탭 → &lt;strong&gt;Add Key → JSON&lt;/strong&gt; → 다운로드&lt;/li&gt;
&lt;li&gt;다운로드한 JSON을 &lt;strong&gt;GitHub Secret&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;GSC_SERVICE_ACCOUNT_KEY&lt;/code&gt;에 &lt;strong&gt;전체 내용&lt;/strong&gt; 붙여넣기&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2) Search Console 권한 부여 (2분)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://search.google.com/search-console&quot;&gt;Search Console&lt;/a&gt; → 대상 속성 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settings → Users and permissions → Add User&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;서비스 계정 이메일(&lt;code class=&quot;language-text&quot;&gt;xxx@yyy.iam.gserviceaccount.com&lt;/code&gt;) 추가, &lt;strong&gt;Restricted&lt;/strong&gt; 권한이면 충분&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3) Telegram Bot (3분)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Telegram &lt;a href=&quot;https://t.me/BotFather&quot;&gt;@BotFather&lt;/a&gt;에게 &lt;code class=&quot;language-text&quot;&gt;/newbot&lt;/code&gt; → HTTP API Token 수신 → &lt;code class=&quot;language-text&quot;&gt;TELEGRAM_BOT_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;생성한 봇에게 &lt;code class=&quot;language-text&quot;&gt;/start&lt;/code&gt; 아무 메시지 (봇이 먼저 말 못 걸기 때문)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://t.me/userinfobot&quot;&gt;@userinfobot&lt;/a&gt;에 &lt;code class=&quot;language-text&quot;&gt;/start&lt;/code&gt; → 본인 chat_id → &lt;code class=&quot;language-text&quot;&gt;TELEGRAM_CHAT_ID&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;코드 구조 — 4개 모듈로 분리&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;scripts/gsc-monitor/
├── package.json              # googleapis + fast-xml-parser
├── run.js                    # 오케스트레이션 (40줄)
└── lib/
    ├── sitemap.js            # sitemap-index → URL 배열
    ├── gsc.js                # URL Inspection API 호출
    ├── diff.js               # 이전 결과와 비교, 요약 계산
    └── telegram.js           # HTML 메시지 빌더 + 전송

.github/workflows/gsc-monitor.yml    # cron + workflow_dispatch
data/indexing-status.json            # 상태 스냅샷 (봇이 자동 커밋)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;lib/sitemap.js — sitemap-index 파싱&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; XMLParser &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fast-xml-parser&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;ignoreAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchXml&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gsc-monitor/1.0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; → HTTP &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;extractAllUrls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;indexUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchXml&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;indexUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; children &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sitemapindex&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;sitemap &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urlSets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchXml&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urls &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; doc &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; urlSets&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urlset&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;lib/gsc.js — URL Inspection API&lt;/h3&gt;
&lt;p&gt;핵심은 &lt;code class=&quot;language-text&quot;&gt;urlInspection.index.inspect&lt;/code&gt; 호출.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; google &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;googleapis&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;serviceAccountKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; auth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GoogleAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;credentials&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceAccountKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;scopes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://www.googleapis.com/auth/webmasters.readonly&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;searchconsole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; auth &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inspectUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; siteUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inspectionUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urlInspection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;requestBody&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; siteUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inspectionUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;languageCode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ko&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inspectionResult&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;indexStatusResult &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inspectionUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;verdict&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; idx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;verdict&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;coverageState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; idx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coverageState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;lastCrawlTime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; idx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastCrawlTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;googleCanonical&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; idx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;googleCanonical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;userCanonical&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; idx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userCanonical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;중요:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;siteUrl&lt;/code&gt; 형식이 속성 유형에 따라 다릅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain property&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;sc-domain:blog.javapark.kr&lt;/code&gt; (권장, 하위 모든 경로 자동 커버)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL prefix property&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/&lt;/code&gt; (프로토콜·www 구분됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;lib/diff.js — 변화 감지&lt;/h3&gt;
&lt;p&gt;단순하지만 핵심 로직. 이전 실행의 URL별 상태와 비교해서 &lt;strong&gt;새로 인덱싱된 것&lt;/strong&gt;, &lt;strong&gt;인덱스에서 제거된 것&lt;/strong&gt;, &lt;strong&gt;canonical 불일치&lt;/strong&gt;를 찾습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INDEXED_STATES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;Submitted and indexed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;Indexed, not submitted in sitemap&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diffResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;previous &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prev &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;previous&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newlyIndexed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newlyDeindexed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canonicalMismatch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; curr &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prevEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; prev&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currIndexed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INDEXED_STATES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coverageState &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prevIndexed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; prevEntry
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INDEXED_STATES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prevEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coverageState &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currIndexed &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;prevIndexed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; newlyIndexed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;curr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;currIndexed &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; prevIndexed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; newlyDeindexed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;curr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;googleCanonical &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userCanonical &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;googleCanonical &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userCanonical
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      canonicalMismatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;curr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; indexed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;INDEXED_STATES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coverageState &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    newlyIndexed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    newlyDeindexed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    canonicalMismatch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    indexed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;indexed &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;lib/telegram.js — HTML 메시지&lt;/h3&gt;
&lt;p&gt;Telegram은 제한적 HTML을 지원합니다. &lt;code class=&quot;language-text&quot;&gt;&amp;lt;b&gt;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&amp;lt;i&gt;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&amp;lt;code&gt;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&amp;lt;a&gt;&lt;/code&gt; 정도.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendTelegram&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chatId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://api.telegram.org/bot&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;token&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/sendMessage&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;chat_id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; chatId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;parse_mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;HTML&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;disable_web_page_preview&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Telegram &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub Actions 워크플로우&lt;/h2&gt;
&lt;p&gt;cron으로 주간 실행 + 수동 트리거(&lt;code class=&quot;language-text&quot;&gt;workflow_dispatch&lt;/code&gt;) 지원.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; GSC Indexing Monitor

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0 0 * * 1&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 매주 월요일 00:00 UTC (09:00 KST)&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;force_notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;변경 없어도 Telegram 알림 전송&quot;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; choice
        &lt;span class=&quot;token key atrule&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;monitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install
        &lt;span class=&quot;token key atrule&quot;&gt;working-directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scripts/gsc&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;monitor
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;audit &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;fund

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Run
        &lt;span class=&quot;token key atrule&quot;&gt;working-directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scripts/gsc&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;monitor
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;GSC_SERVICE_ACCOUNT_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GSC_SERVICE_ACCOUNT_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.TELEGRAM_BOT_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;TELEGRAM_CHAT_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.TELEGRAM_CHAT_ID &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;GSC_SITE_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sc&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;blog.javapark.kr
          &lt;span class=&quot;token key atrule&quot;&gt;SITE_ORIGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//blog.javapark.kr
          &lt;span class=&quot;token key atrule&quot;&gt;FORCE_NOTIFY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.force_notify &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node run.js

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Commit status
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          git config user.name &quot;gsc-monitor-bot&quot;
          git config user.email &quot;bot@users.noreply.github.com&quot;
          git add data/indexing-status.json
          if git diff --staged --quiet; then
            echo &quot;No changes&quot;
          else
            git commit -m &quot;chore(gsc): update $(date -u +%Y-%m-%d)&quot;
            git push
          fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;첫 실행 결과 — 57/0 사태&lt;/h2&gt;
&lt;p&gt;준비가 끝나고 &lt;strong&gt;수동 트리거&lt;/strong&gt;로 첫 실행을 돌렸습니다. &lt;code class=&quot;language-text&quot;&gt;data/indexing-status.json&lt;/code&gt;에 저장된 결과:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;generatedAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-04-20T16:06:00.795Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;siteUrl&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sc-domain:blog.javapark.kr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;total&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;57&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;indexed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;rate&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;stillPending&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;57&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;57개 URL 전부 다음과 같은 상태:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;verdict&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;NEUTRAL&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;coverageState&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Google에는 아직 알려지지 않은 URL입니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;indexingState&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;INDEXING_STATE_UNSPECIFIED&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;lastCrawlTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;googleCanonical&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&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;이 상태의 의미&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Google에는 아직 알려지지 않은 URL입니다&quot;&lt;/code&gt;는 구글 봇이 한 번도 이 URL을 방문하지 않았다는 뜻입니다. 이게 광범위하게 발생하면 보통 다음 4가지 중 하나입니다.&lt;/p&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;&lt;strong&gt;① sitemap이 GSC에 제출되지 않음&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GSC → &lt;code class=&quot;language-text&quot;&gt;Sitemaps&lt;/code&gt; 메뉴에서 sitemap-index.xml &lt;strong&gt;수동 등록&lt;/strong&gt; 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;② 속성이 최근 추가되었고 아직 크롤링 미실행&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GSC 속성 &quot;추가 일자&quot; 확인. 2주 이내면 정상 — 기다림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;**③ 사이트가 최근 마이그레이션됨&lt;/td&gt;
&lt;td&gt;도메인 변경, 플랫폼 이관(GitHub Pages → Netlify 등) 후 새 도메인의 크롤링 히스토리 부족**&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;④ robots.txt가 광범위 차단&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/robots.txt&lt;/code&gt; 확인. &lt;code class=&quot;language-text&quot;&gt;Disallow: /&lt;/code&gt;가 있으면 전면 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;제 경우는 ①과 ③의 조합이었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최근 &lt;code class=&quot;language-text&quot;&gt;javapark.github.io&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;blog.javapark.kr&lt;/code&gt; 도메인으로 Netlify 이관&lt;/li&gt;
&lt;li&gt;새 도메인 속성을 GSC에 추가는 했지만 &lt;strong&gt;sitemap 제출은 안 함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/fcae625dc48c86cb950d8ba54010506b/64756/diagram-findings.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 50.23255813953489%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6UlEQVR42h2RSZabMABEuUJju7FAgAySEJMkJgmJwVO323E6yT4v29z/CnHnvb+oTVUtvuP6NUs6Qkc53nn7llYnPT0afe/sYzz+QvkMiIaZCZmNiiliFlID6RgQBVLlbPwqQ01OlG6vslroc6WetflYzj+r6T1t15AqVNqwmEFm/f8AOgIy7pPBcUFZYVUQBaEMAw4hD0DNy/7+8W1cLnKwTT9JMzP7ELc/B/3pNx+BvO3rq/dV9itBhiJfXKR2XvHq10HYZLRt1arMpVXHgo952SO+Yv0j6e5+efLydUvnHeqd11Ac0gFThXGX4oEQXeU2Y7PovhfinZYXWp4TcWHVUtZLyuwBK5T0cTK8Ro0DYpEzlTNNcBvFPUJdADmrruf7X3P8ncsfmfjE/CH55WTugh8LZkjSYdR4oXiWpayGttai6HgxiMpQ2iNiomw95EfEVj8dQWogVhB3MVFBLPewBpBvIXe8uBmkuppuHZqT7i52ta2NiQ4y42MNsAJEeUTtcL9L+m3SbVGzjeQGCjeonX3aA6ZArkFhgqeSYY3tmcw3PN3wfEumN2SuyL4dpvfYXnfV6GaDS/uXpHnxa+fp2qdfP5A9ZU5RvYb1EpXzM8Pc+rnx2bj/QnuZ2iTtJpYvId9EjRvwf0SaaD9CeFncAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/fcae625dc48c86cb950d8ba54010506b/a1b3b/diagram-findings.webp 215w,
/static/fcae625dc48c86cb950d8ba54010506b/fc700/diagram-findings.webp 430w,
/static/fcae625dc48c86cb950d8ba54010506b/521f7/diagram-findings.webp 860w,
/static/fcae625dc48c86cb950d8ba54010506b/cbd37/diagram-findings.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/fcae625dc48c86cb950d8ba54010506b/f7886/diagram-findings.png 215w,
/static/fcae625dc48c86cb950d8ba54010506b/bc750/diagram-findings.png 430w,
/static/fcae625dc48c86cb950d8ba54010506b/3e3fe/diagram-findings.png 860w,
/static/fcae625dc48c86cb950d8ba54010506b/64756/diagram-findings.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/fcae625dc48c86cb950d8ba54010506b/3e3fe/diagram-findings.png&apos; alt=&apos;첫 실행 0/57 인덱싱 결과와 원인 진단 매트릭스 — sitemap 미제출·속성 신규·도메인 이관·robots.txt 4가지 후보&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;첫 실행 0/57 인덱싱 결과와 원인 진단 매트릭스 — sitemap 미제출·속성 신규·도메인 이관·robots.txt 4가지 후보&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;h3&gt;해결 순서&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;robots.txt&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;Disallow: /&lt;/code&gt; 없는지 확인 → &lt;strong&gt;통과&lt;/strong&gt; (문제 없음)&lt;/li&gt;
&lt;li&gt;GSC에서 &lt;code class=&quot;language-text&quot;&gt;Sitemaps&lt;/code&gt; 메뉴 → &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/sitemap-index.xml&lt;/code&gt; &lt;strong&gt;수동 제출&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;주요 페이지 5~10개 선택해서 &lt;code class=&quot;language-text&quot;&gt;URL 검사&lt;/code&gt; → &lt;strong&gt;색인 생성 요청&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;1~2주 기다리면서 &lt;strong&gt;이 모니터의 주간 리포트로 인덱싱 진행률 추적&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;모니터가 없었다면 이 진단 자체가 시작되지 않았을 겁니다. 자동화의 진짜 가치는 여기 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Telegram 메시지 실제 모습&lt;/h2&gt;
&lt;p&gt;변화 발생 시 오는 메시지 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;🔔 GSC Indexing Monitor
2026-04-28 · https://blog.javapark.kr

✅ 신규 인덱싱: 3건
  • /devops/k8s-understanding/
  • /devops/kubectl-essential-commands/
  • /devops/k9s-essential-guide/

⚠️ 인덱싱 대기: 54건

📈 전체: 57개 중 3개 인덱싱 (5.3%)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;한 화면에 &lt;strong&gt;현재 상태&lt;/strong&gt;와 &lt;strong&gt;지난 주 대비 변화&lt;/strong&gt;가 같이 잡힙니다. 주 1회 1분만 확인하면 블로그 SEO 건강 상태 파악 완료.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code 활용 회고 — 30분 구축의 비결&lt;/h2&gt;
&lt;p&gt;이 시스템을 &lt;strong&gt;Claude Code로 30분 만에 완성&lt;/strong&gt;했습니다. 핵심 요인 3가지.&lt;/p&gt;
&lt;h3&gt;1) Subagent로 API 문서 조사&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;googleapis&lt;/code&gt; 라이브러리의 Search Console API 스펙을 Subagent에게 조사시키고, 주요 메서드·응답 구조만 요약받았습니다. 메인 세션은 그 결과를 받아 코드만 작성하면 됐습니다. &lt;a href=&quot;/subagent-essential-guide/&quot;&gt;Subagent 편&lt;/a&gt;의 &quot;컨텍스트 격리&quot; 가치가 여기서 직접 드러납니다.&lt;/p&gt;
&lt;h3&gt;2) 모듈 단위 병렬 생성&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;sitemap.js&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;gsc.js&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;diff.js&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;telegram.js&lt;/code&gt;를 각각 독립 모듈로 지정해 &lt;strong&gt;한 번에 4개 파일 생성&lt;strong&gt;. Claude Code가 파일 간 인터페이스만 맞춰주면 됩니다. 만약 하나의 긴 파일로 작성하다 에러 나면 전체 디버깅이 필요한데, 분리돼 있으면 &lt;/strong&gt;해당 파일만&lt;/strong&gt; 재작성하면 됩니다.&lt;/p&gt;
&lt;h3&gt;3) pre-commit 훅까지 자동 대응&lt;/h3&gt;
&lt;p&gt;레포의 husky + prettier + eslint가 자동 실행되는 환경이었는데, eslint의 &lt;code class=&quot;language-text&quot;&gt;Set/Map is not defined&lt;/code&gt; 같은 에러를 Claude Code가 즉시 파악하고 &lt;strong&gt;로컬 &lt;code class=&quot;language-text&quot;&gt;.eslintrc.json&lt;/code&gt;으로 Node ESM 환경 선언&lt;/strong&gt;으로 해결했습니다. 사람이 직접 ESLint 설정을 뜯어보는 시간이 절약됐습니다.&lt;/p&gt;
&lt;p&gt;&quot;도구가 도구를 만든다&quot;는 느낌이 강하게 드는 작업이었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;확장 아이디어 5가지&lt;/h2&gt;
&lt;p&gt;기본 시스템이 안정되면 붙일 수 있는 것들.&lt;/p&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;&lt;strong&gt;일 단위 cron&lt;/strong&gt;으로 변경&lt;/td&gt;
&lt;td&gt;이슈 감지 속도 7배 빨라짐 (API quota 여유 확인 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;블로그 내부 &lt;code class=&quot;language-text&quot;&gt;/insights&lt;/code&gt; 대시보드&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;indexing-status.json을 Gatsby 페이지로 시각화 (독자에게도 공개 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Canonical 불일치 즉시 알림&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중복 콘텐츠 판정으로 SEO 누수되기 전에 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;**IndexNow 프로토콜 병행&lt;/td&gt;
&lt;td&gt;Bing·Yandex에는 즉시 푸시** 가능 (Google은 미지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;**배포 훅 연동&lt;/td&gt;
&lt;td&gt;Netlify 배포 성공 시 30분 뒤 해당 URL만 타겟팅 체크**&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;가장 투자 대비 효과가 큰 건 &lt;strong&gt;&quot;블로그 내부 대시보드&quot;&lt;/strong&gt;입니다. 본인 블로그의 SEO 현황을 공개하는 것 자체가 기술 블로그의 차별화 포인트가 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;한계와 현실&lt;/h2&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;API quota: &lt;strong&gt;일당 2000, 분당 600&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;개인 블로그는 여유. URL 1000+ 넘어가면 배치 분할 고려&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱싱 상태는 &lt;strong&gt;실시간 아님&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;구글 내부 캐시 — 몇 시간~며칠 지연 있을 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API는 &lt;strong&gt;읽기 전용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&quot;지금 인덱싱해줘&quot; 요청은 별도(Indexing API, 제한적) 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram 메시지 &lt;strong&gt;4096자 제한&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;큰 이슈가 많으면 메시지 분할 필요 (현재는 미구현)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. 이거 돌리는 데 돈이 드나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;무료입니다.&lt;/strong&gt; Google Search Console API는 무료 할당량(일 2000회) 내에서 사용, GitHub Actions는 public 레포는 무제한, Telegram Bot도 무료. 개인 블로그 규모에서는 &lt;strong&gt;0원으로 운영&lt;/strong&gt; 가능합니다.&lt;/p&gt;
&lt;h3&gt;Q. sitemap을 여러 개 쓰거나 분할한 경우는?&lt;/h3&gt;
&lt;p&gt;코드에서 &lt;code class=&quot;language-text&quot;&gt;sitemap-index.xml&lt;/code&gt;을 먼저 fetch하고, 그 안의 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;sitemap&gt;&lt;/code&gt; 자식들을 모두 순회합니다. 대부분의 Gatsby/Hugo/Jekyll 기본 설정이 이 구조를 쓰므로 별도 작업 불필요.&lt;/p&gt;
&lt;h3&gt;Q. Google Indexing API로 &quot;지금 인덱싱&quot; 요청도 자동화할 수 있나요?&lt;/h3&gt;
&lt;p&gt;가능은 한데 &lt;strong&gt;공식적으로 &lt;code class=&quot;language-text&quot;&gt;JobPosting&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;BroadcastEvent&lt;/code&gt; 타입만 지원합니다. 일반 블로그 글은 ToS 위반이 됩니다. 많은 사이트가 편법으로 쓰지만 계정 제재 리스크가 있어 비권장&lt;/strong&gt;. 대신 &lt;strong&gt;Bing의 IndexNow&lt;/strong&gt;는 공식 지원하므로 그쪽으로 자동화하는 게 안전.&lt;/p&gt;
&lt;h3&gt;Q. Canonical 불일치가 뭔가요?&lt;/h3&gt;
&lt;p&gt;내가 선언한 canonical URL(&lt;code class=&quot;language-text&quot;&gt;&amp;lt;link rel=&quot;canonical&quot;&gt;&lt;/code&gt;)과 Google이 실제로 선택한 canonical이 다른 경우입니다. 예: &lt;code class=&quot;language-text&quot;&gt;/post/&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;/post/index.html&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;http://&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;https://&lt;/code&gt;. 불일치가 있으면 내 URL 대신 Google 선택 URL이 노출되며, SEO 가치가 분산됩니다. 모니터가 이걸 자동 감지해 알려주는 게 실용적 가치 중 하나.&lt;/p&gt;
&lt;h3&gt;Q. &lt;code class=&quot;language-text&quot;&gt;Domain property&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;URL prefix property&lt;/code&gt; 차이는요?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain property&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;sc-domain:blog.javapark.kr&lt;/code&gt; — DNS TXT 인증, &lt;strong&gt;하위 모든 경로·프로토콜·www&lt;/strong&gt; 자동 커버. 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL prefix property&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/&lt;/code&gt; — HTML 파일 인증, 프로토콜·www 구분됨. 더 제약적.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;신규 시작하면 Domain property로 가세요. 이미 URL prefix로 되어있으면 병행 등록 후 Domain으로 이관.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 블로그 SEO 건강검진을 자동화하라&lt;/h2&gt;
&lt;p&gt;&quot;구글에 내 글이 어떻게 보이는지&quot;를 &lt;strong&gt;사람이 수동으로 확인하는 일은 이제 그만&lt;/strong&gt;해도 됩니다. API와 Cron, 그리고 Telegram 봇 하나면 주간 자동 건강검진이 가능합니다.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GSC URL Inspection API는 &lt;strong&gt;무료 + 강력&lt;/strong&gt;, 개인 블로그는 quota 여유&lt;/li&gt;
&lt;li&gt;GitHub Actions cron + JSON 상태 저장으로 &lt;strong&gt;추이 추적&lt;/strong&gt; 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code로 30분 구축&lt;/strong&gt; — 모듈 분리 + Subagent 활용이 핵심&lt;/li&gt;
&lt;li&gt;첫 실행에서 &quot;57/0&quot; 같은 &lt;strong&gt;예상치 못한 사태&lt;/strong&gt;를 발견하는 게 자동화의 진짜 가치&lt;/li&gt;
&lt;li&gt;확장: 일 단위 cron, 블로그 내부 대시보드, IndexNow 연동까지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;다음에 써볼 만한 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Gatsby 페이지로 SEO 대시보드 노출하기 (indexing-status.json 시각화)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; IndexNow로 Bing·Yandex 즉시 인덱싱 요청&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Netlify Deploy Hook과 연동해 &quot;배포 후 30분 뒤 해당 URL만 체크&quot;&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Slack·Discord 다중 채널 동시 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;관련 포스팅&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/subagent-essential-guide/&quot;&gt;Claude Code Subagent 완벽 가이드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/beginner-guide-2026/&quot;&gt;Claude Code 입문 가이드 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/token-saving-advanced/&quot;&gt;Claude Code 토큰 절약 고급편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분의 블로그는 구글에 얼마나 잘 잡히고 있으신가요? GSC를 &lt;strong&gt;&quot;문제 생긴 후 확인하는 곳&quot;&lt;/strong&gt;에서 &lt;strong&gt;&quot;주간 리포트로 받아보는 건강검진&quot;&lt;/strong&gt;으로 바꿔보세요. 구축은 30분, 효과는 몇 달치입니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[nginx-ingress → Traefik 마이그레이션 실전 — 무중단으로 Ingress Controller 교체하기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "7편대로 ingress-nginx로 HTTPS까지 다 자동화했는데, 팀에서 Traefik으로 바꾸자는 이야기가 나왔습니다. 다시 다 엎어야 하나요?" 솔직히 저도 그랬습니다. 운영 중인 Controller…]]></description><link>https://blog.javapark.kr/nginx-to-traefik-migration/</link><guid isPermaLink="false">https://blog.javapark.kr/nginx-to-traefik-migration/</guid><pubDate>Tue, 21 Apr 2026 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;7편대로 ingress-nginx로 HTTPS까지 다 자동화했는데, 팀에서 Traefik으로 바꾸자는 이야기가 나왔습니다. 다시 다 엎어야 하나요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. 운영 중인 Controller를 바꾸는 건 &lt;strong&gt;진입점을 바꾸는 일&lt;/strong&gt; — 잘못하면 서비스 전체 접근 불가 상황이 벌어집니다. &quot;어차피 annotation이랑 YAML 다시 쓰는 게 덜 나을까&quot; 생각도 들고, 한 번 결정하면 돌이키기 어려워 보입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Controller 마이그레이션은 &quot;병렬 운영 기간&quot;만 제대로 두면 무중단으로 됩니다.&lt;/strong&gt; nginx와 Traefik을 &lt;strong&gt;동시에&lt;/strong&gt; 클러스터에 두고, Ingress별로 &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt;만 바꿔가며 점진적으로 전환하면 됩니다. cert-manager는 &lt;strong&gt;그대로 재사용&lt;/strong&gt; 가능합니다. 오늘은 이 순서대로, nginx vs Traefik 차이부터 Traefik 설치, 실제 Ingress 전환, 미들웨어로 annotation 대체, 롤백 전략까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt; · &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 완벽 가이드&lt;/a&gt; · &lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD GitOps 가이드&lt;/a&gt; · &lt;a href=&quot;/ingress-cert-manager-guide/&quot;&gt;Ingress + cert-manager 가이드&lt;/a&gt;부터 읽으시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 Traefik으로? — 현실적 동기 5가지&lt;/h2&gt;
&lt;p&gt;먼저 &lt;strong&gt;&quot;굳이 바꿀 필요가 있나?&quot;&lt;/strong&gt;부터 정리합니다. 다음 중 2개 이상 공감되면 전환 고려 가치가 있습니다.&lt;/p&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;&lt;strong&gt;자동 서비스 디스커버리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker·Consul·Kubernetes 라벨만으로 라우팅이 자동 구성됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CRD 기반 선언&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;annotation으로 설정이 뒤얽히는 nginx와 달리 &lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;Middleware&lt;/code&gt;로 &lt;strong&gt;깔끔히 분리&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;네이티브 Gateway API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;차세대 Ingress 표준을 Traefik이 가장 먼저 native 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;내장 Dashboard·Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;별도 도구 없이 트래픽·라우터 시각화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hot reload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;설정 변경 시 downtime 없이 재적용 (nginx-ingress도 되지만 동작 검증 편함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;반대로 &lt;strong&gt;nginx-ingress를 유지할 이유&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이미 안정적 운영 중, 생태계·문서 가장 풍부&lt;/li&gt;
&lt;li&gt;단순 기능만 쓰고 있음 (Traefik 장점이 실질적 가치를 주지 않음)&lt;/li&gt;
&lt;li&gt;팀이 nginx 설정·annotation에 깊이 숙련됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&quot;이유 없는 전환은 비용&quot;&lt;/strong&gt;이라는 걸 먼저 짚어둡니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;nginx vs Traefik — 설계 철학의 차이&lt;/h2&gt;
&lt;p&gt;차이를 한 줄로: &lt;strong&gt;annotation 중심 vs CRD 중심&lt;/strong&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;ingress-nginx&lt;/th&gt;
&lt;th&gt;Traefik&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주 설정 방식&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ingress&lt;/code&gt; + &lt;strong&gt;annotations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;&lt;/strong&gt; CRD (또는 Ingress)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;미들웨어(rate·CORS)&lt;/td&gt;
&lt;td&gt;annotation 값&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Middleware&lt;/code&gt;&lt;/strong&gt; CRD로 재사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;설정 가독성&lt;/td&gt;
&lt;td&gt;annotation 많아지면 한 리소스가 뚱뚱해짐&lt;/td&gt;
&lt;td&gt;라우터·미들웨어 분리, &lt;strong&gt;관심사 분리&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커뮤니티&lt;/td&gt;
&lt;td&gt;가장 크다&lt;/td&gt;
&lt;td&gt;충분히 큼, 공식 docs 친절&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서비스 디스커버리&lt;/td&gt;
&lt;td&gt;Kubernetes Ingress만&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;다중 Provider&lt;/strong&gt; (K8S·Docker·Consul 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;별도 Prometheus+Grafana 조합&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;내장&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;td&gt;지원 (후발)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;선도&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;표준 Ingress 호환&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓ (하위 호환 모드)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;즉 Traefik은 &lt;strong&gt;&quot;설정을 객체로 분해&quot;&lt;/strong&gt;하는 방향이고, nginx는 &lt;strong&gt;&quot;한 Ingress 리소스에 옵션 총집합&quot;&lt;/strong&gt; 방향입니다. 취향 + 팀 규모의 문제.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/0cc675dbfe98e30205c9d804332f4f06/64756/diagram-comparison.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJUlEQVR42iWS6W7jIBCA/QY1cS7j+8KAMeADx07iOEmz6XbVrtT+2ePHvv9jLE6lTyM08M2MAMOEYhG2CWI5YSlVkJ2TcmTtM64uov9Bqmc7US4aNB7e+3ivo4t6J9tpDAB5iKXktGSFqNqkmlg9NruLUFMzfEPtyzYbIN47xcljk1ucHDpCcoT4aKedYTnFZcBv12LoOMKckhITgYigXCWIw+EjvP0Lpz9e892t7q68Q37TOPy2STpj6bIUN4SrvKjCTIapiDTosUaVL29J9xaoV5uetvlhkx/WaL/KBs06bmfZi5s46h2ngn619rhlUwsWC5vCWDr5PiDHZVKbDgOQmV/YxUKfjBpj5fMgqHp8HegVRjXQGzMUbGfZI6Ov5aheOBw4pQkf2Az4chU2xiYQXizTtENoZ0dy45d2wO1QbAPuo8bBB33D66y1PP7o+WBLgSeXQW1swyrGMsGVBhUtZoqUHeE9lT0qO4eMAbtAelwlyorUItS0IGh0tHxp2FGdkjqjzcPs8JcpNDvElI0GiAYrrJ88YboCuBK4wtTze9XCEwaMG21qJy9UXioyaz3hO53JCrWMW5h0Xj7oL7HxahPyJ1g+2YX5qGLAtGHqON3e99OLdmZT9PMITCW4huJcP/9St9/F+LmmV4ucQD6C7AiSQRcy3LRt2vF8/3l7/ai6STecp2D62dsolxG/qPtfOn0mwzsg04KcQX7SspUdgMP/AxTedJ4+NszmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/0cc675dbfe98e30205c9d804332f4f06/a1b3b/diagram-comparison.webp 215w,
/static/0cc675dbfe98e30205c9d804332f4f06/fc700/diagram-comparison.webp 430w,
/static/0cc675dbfe98e30205c9d804332f4f06/521f7/diagram-comparison.webp 860w,
/static/0cc675dbfe98e30205c9d804332f4f06/cbd37/diagram-comparison.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/0cc675dbfe98e30205c9d804332f4f06/f7886/diagram-comparison.png 215w,
/static/0cc675dbfe98e30205c9d804332f4f06/bc750/diagram-comparison.png 430w,
/static/0cc675dbfe98e30205c9d804332f4f06/3e3fe/diagram-comparison.png 860w,
/static/0cc675dbfe98e30205c9d804332f4f06/64756/diagram-comparison.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/0cc675dbfe98e30205c9d804332f4f06/3e3fe/diagram-comparison.png&apos; alt=&apos;nginx annotation 방식 vs Traefik CRD/Middleware 방식 비교 — 같은 기능을 구현하는 두 접근법&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;nginx annotation 방식 vs Traefik CRD/Middleware 방식 비교 — 같은 기능을 구현하는 두 접근법&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마이그레이션 전략 — 4단계 병렬 운영&lt;/h2&gt;
&lt;p&gt;핵심은 &lt;strong&gt;&quot;한 번에 교체하지 않는다&quot;&lt;/strong&gt;입니다. 단계를 나눕니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[1단계] Traefik 설치 (nginx 그대로 둠)
   ↓
[2단계] 비운영 Ingress 1~2개 전환 · 검증
   ↓
[3단계] 운영 Ingress 점진 전환 (도메인별·서비스별)
   ↓
[4단계] nginx 제거&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/556d61074bc8d2c3a426735fa7494a0f/64756/diagram-migration-flow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVR42hXSiY6jIBgAYN9gd9pOD7UiHiiXV1EUUGsdp7PHZN//bZYmXwiQ/Ef+/M6PC9+l4z6b/aRIufCpps3C27VWX6hcTrB1095Fg58N10wFub7m6mqf9jOVzt7jgMqgXEq50nYMy7mQWyE/a/2LiwWSDpI+oj2kKiinKx9tdsvDg5tI593nBUZJXmfNBrCGWIVEAWJCLD8W0fWSlJLXEjcKjJvX3t3beLmNbmUuceccguqMdcjGWv22QqIBVrY3gIem7Vk9pFxlhUKFRsUzog9A7laQT+eodQ6g2fGnR2csNlSvkE1xcYdsjpgyY1uJjpQdrVpcKVT9i+kXxBvMN5A9TlA4RyBAukVkqWxl843qB8AmwMaepfiblX9iuloJXcpOZ7xLqdVGuD2FN+cUthH6DPMZNWtSPSI+h2wKiIHEcPE7Zl8xtam3mK1ULnkpERMZEzEWR9A4p0gcGwOquTHfkM8vbIqKe8Smorqzcob5EOVDQvq607jqbTCir+D3oHbOcXsutUd1SIwtCMj4Qic7cMTGhBqQDyAbQjxkwzMuFMQC5iJAt4NfOnZol6h1Y+ml0kevTbhmOsh6e3eT/hxL6xjL90geYLcPrXZ3bd68audx55LICxlcqjxmfD5ei9EvJ6TX1DzB8AnbNZJb2H+ci/GA1SEfdmm3S8QONj9d7rio94iybV/Z+FogZjxu/Gryy9Hl2mP6wrRbmCPRe9Tvkdwl7Rts9pF484r/k+p9xErIoiIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/556d61074bc8d2c3a426735fa7494a0f/a1b3b/diagram-migration-flow.webp 215w,
/static/556d61074bc8d2c3a426735fa7494a0f/fc700/diagram-migration-flow.webp 430w,
/static/556d61074bc8d2c3a426735fa7494a0f/521f7/diagram-migration-flow.webp 860w,
/static/556d61074bc8d2c3a426735fa7494a0f/cbd37/diagram-migration-flow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/556d61074bc8d2c3a426735fa7494a0f/f7886/diagram-migration-flow.png 215w,
/static/556d61074bc8d2c3a426735fa7494a0f/bc750/diagram-migration-flow.png 430w,
/static/556d61074bc8d2c3a426735fa7494a0f/3e3fe/diagram-migration-flow.png 860w,
/static/556d61074bc8d2c3a426735fa7494a0f/64756/diagram-migration-flow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/556d61074bc8d2c3a426735fa7494a0f/3e3fe/diagram-migration-flow.png&apos; alt=&apos;nginx→Traefik 마이그레이션 4단계 타임라인 — 설치·검증·점진 전환·제거 단계별 클러스터 상태&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;nginx→Traefik 마이그레이션 4단계 타임라인 — 설치·검증·점진 전환·제거 단계별 클러스터 상태&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 장치: &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;쿠버네티스는 한 클러스터에 여러 Controller를 둘 수 있습니다. 각 Ingress가 &lt;code class=&quot;language-text&quot;&gt;ingressClassName: nginx&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;ingressClassName: traefik&lt;/code&gt;으로 &lt;strong&gt;자기 Controller를 지정&lt;/strong&gt;합니다. 이 값만 바꾸면 &lt;strong&gt;그 Ingress 하나만&lt;/strong&gt; Traefik으로 넘어갑니다. 나머지는 영향 없음.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 기존 nginx로 처리되던 Ingress&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx     &lt;span class=&quot;token comment&quot;&gt;# ← 이걸&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Traefik으로 전환&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik   &lt;span class=&quot;token comment&quot;&gt;# ← 이걸로&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;DNS 레코드나 LB IP는 &lt;strong&gt;바꾸지 않습니다&lt;/strong&gt; (후술).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1단계 — Traefik 설치&lt;/h2&gt;
&lt;p&gt;Helm으로 설치, nginx와 &lt;strong&gt;다른 네임스페이스&lt;/strong&gt;에 둡니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; traefik https://traefik.github.io/charts
helm repo update

helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; traefik traefik/traefik &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt; traefik --create-namespace &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ingressClass.enabled&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ingressClass.isDefaultClass&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;false &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;service.type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;LoadBalancer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심 포인트:&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;ingressClass.enabled=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;traefik&lt;/code&gt;라는 IngressClass 리소스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ingressClass.isDefaultClass=false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;기본값으로 두지 않음&lt;/strong&gt; — &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt; 미지정 Ingress가 자동으로 Traefik으로 가지 않도록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;service.type=LoadBalancer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;새 LoadBalancer&lt;/strong&gt;(별도 공인 IP) 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;설치 확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get svc &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; traefik
&lt;span class=&quot;token comment&quot;&gt;# traefik    LoadBalancer   &amp;lt;ClusterIP&gt;   &amp;lt;NEW_EXTERNAL_IP&gt;   80:...,443:...&lt;/span&gt;

kubectl get ingressclass
&lt;span class=&quot;token comment&quot;&gt;# NAME       CONTROLLER                     AGE&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# nginx      k8s.io/ingress-nginx           90d&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# traefik    traefik.io/ingress-controller  1m&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 &lt;strong&gt;IP 두 개가 공존&lt;/strong&gt;합니다. 하나는 nginx용(기존 DNS 연결), 하나는 Traefik용(아직 연결 안 됨).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2단계 — 비운영 Ingress로 검증&lt;/h2&gt;
&lt;p&gt;dev·staging 도메인 하나를 골라서 먼저 Traefik으로 전환합니다.&lt;/p&gt;
&lt;h3&gt;2-a) IngressClass만 변경 (가장 간단한 전환)&lt;/h3&gt;
&lt;p&gt;기존 &lt;code class=&quot;language-text&quot;&gt;dev.javapark.kr&lt;/code&gt; Ingress가 이랬다면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networking.k8s.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ingress
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/force-ssl-redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# nginx 전용&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dev.javapark.kr&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /
            &lt;span class=&quot;token key atrule&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Prefix
            &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
                &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;DNS를 새 IP(Traefik LB)로 바꾸고&lt;/strong&gt;, &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;traefik&lt;/code&gt;으로:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik &lt;span class=&quot;token comment&quot;&gt;# ← 변경&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# 나머지는 동일 — Traefik이 기본 Ingress도 그대로 처리&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;HTTPS는 cert-manager가 그대로 해줍니다.&lt;/strong&gt; ClusterIssuer의 &lt;code class=&quot;language-text&quot;&gt;solvers&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;class: nginx&lt;/code&gt;로 되어 있다면 &lt;code class=&quot;language-text&quot;&gt;class: traefik&lt;/code&gt;으로 수정한 ClusterIssuer를 &lt;strong&gt;하나 더&lt;/strong&gt; 만들거나, 기존 ClusterIssuer를 공용으로 쓸 수 있게 &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt; 명시 없이 두는 방법이 있습니다.&lt;/p&gt;
&lt;h3&gt;2-b) ClusterIssuer 준비&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ClusterIssuer
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;traefik
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;acme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//acme&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v02.api.letsencrypt.org/directory
    &lt;span class=&quot;token key atrule&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; you@example.com
    &lt;span class=&quot;token key atrule&quot;&gt;privateKeySecretRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;traefik&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key
    &lt;span class=&quot;token key atrule&quot;&gt;solvers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;http01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik &lt;span class=&quot;token comment&quot;&gt;# ← Traefik용&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;검증 방법:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# DNS 전파 확인&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;dig&lt;/span&gt; dev.javapark.kr +short
&lt;span class=&quot;token comment&quot;&gt;# → Traefik LB IP 나오는지&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# HTTPS 접근&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-vI&lt;/span&gt; https://dev.javapark.kr
&lt;span class=&quot;token comment&quot;&gt;# → 인증서 발급 + 정상 응답 확인&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Traefik 내부 라우터 확인 (Dashboard 활용, 후술)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;3단계 — 운영 Ingress 점진 전환&lt;/h2&gt;
&lt;p&gt;검증이 끝나면 본격 전환. &lt;strong&gt;한 번에 전부 바꾸지 마세요.&lt;/strong&gt; 권장 순서:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;트래픽 낮은 시간대&lt;/strong&gt; (새벽) 작업&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도메인별&lt;/strong&gt; 또는 &lt;strong&gt;팀별&lt;/strong&gt;로 나눠서 2~3일 간격&lt;/li&gt;
&lt;li&gt;각 전환 후 &lt;strong&gt;모니터링 1시간&lt;/strong&gt; (에러율·응답시간·인증서 상태)&lt;/li&gt;
&lt;li&gt;문제 발생 시 &lt;strong&gt;즉시 롤백&lt;/strong&gt; (DNS 또는 ingressClassName 되돌림)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;annotation을 Traefik Middleware로 교체&lt;/h3&gt;
&lt;p&gt;이 단계가 가장 신경 쓰이는 부분입니다. nginx-ingress annotation을 Traefik 문법으로 맵핑:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;nginx annotation&lt;/th&gt;
&lt;th&gt;Traefik 대응&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/force-ssl-redirect: &quot;true&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Middleware&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;redirectScheme&lt;/code&gt; (HTTPS 강제) 또는 entrypoint 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/proxy-body-size: 50m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Middleware &lt;code class=&quot;language-text&quot;&gt;buffering.maxRequestBodyBytes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/rewrite-target: /$2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Middleware &lt;code class=&quot;language-text&quot;&gt;replacePathRegex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/enable-cors: &quot;true&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Middleware &lt;code class=&quot;language-text&quot;&gt;headers&lt;/code&gt; (Access-Control-*)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/rate-limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Middleware &lt;code class=&quot;language-text&quot;&gt;rateLimit&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Middleware 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Middleware
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; force&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;https
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;redirectScheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https
    &lt;span class=&quot;token key atrule&quot;&gt;permanent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Middleware
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cors&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headers
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;accessControlAllowOriginList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://app.example.com&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;accessControlAllowMethods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; GET
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; POST
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; OPTIONS
    &lt;span class=&quot;token key atrule&quot;&gt;accessControlMaxAge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;86400&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ingress에 연결 (annotation으로 붙이는 방식):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;traefik.ingress.kubernetes.io/router.middlewares&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;force&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;https@kubernetescrd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cors&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headers@kubernetescrd&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; Middleware는 &lt;strong&gt;네임스페이스 리소스&lt;/strong&gt;라 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;namespace&gt;-&amp;lt;name&gt;@kubernetescrd&lt;/code&gt; 형식의 정식 이름으로 참조해야 합니다. 오타 1위 원인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;IngressRoute CRD로 점진적 전환 (선택)&lt;/h3&gt;
&lt;p&gt;더 풍부한 기능을 쓰려면 &lt;code class=&quot;language-text&quot;&gt;Ingress&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;로 바꿉니다. 같은 리소스를 Traefik 네이티브 방식으로:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; IngressRoute
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;entryPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; websecure
  &lt;span class=&quot;token key atrule&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Host(`api.javapark.kr`) &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; PathPrefix(`/v1`)
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Rule
      &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
          &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;middlewares&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cors&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headers
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api
  &lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Ingress와 IngressRoute를 섞어 써도&lt;/strong&gt; Traefik은 둘 다 처리합니다. 서두르지 말고 기존 Ingress는 그대로 두고, 새 서비스부터 IngressRoute로 쓰는 게 좋은 전략입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Traefik Dashboard — 라우팅 디버깅의 필수 도구&lt;/h2&gt;
&lt;p&gt;기본 설치 시 비활성화되어 있습니다. Helm values로 켜세요:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# values-traefik.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;ingressRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;dashboard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;matchRule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Host(`traefik.javapark.kr`)
    &lt;span class=&quot;token key atrule&quot;&gt;entryPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;websecure&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; traefik&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dashboard&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;업그레이드:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm upgrade traefik traefik/traefik &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; traefik &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values-traefik.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dashboard에서 보이는 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 등록된 &lt;strong&gt;Router&lt;/strong&gt; 목록 (도메인·경로 기반)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service&lt;/strong&gt; (백엔드) 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Middleware&lt;/strong&gt; 체인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS 인증서&lt;/strong&gt; 요약&lt;/li&gt;
&lt;li&gt;실시간 &lt;strong&gt;메트릭스&lt;/strong&gt; (요청 수·응답 시간·에러율)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;보안 경고:&lt;/strong&gt; Dashboard는 &lt;strong&gt;반드시 인증 걸기&lt;/strong&gt;. &lt;code class=&quot;language-text&quot;&gt;BasicAuth&lt;/code&gt; middleware + cert-manager HTTPS로 감싸고, &lt;strong&gt;IP 화이트리스트&lt;/strong&gt;까지 걸면 안전합니다. 기본 상태로 공개하면 라우팅 정보가 노출됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;4단계 — nginx-ingress 제거&lt;/h2&gt;
&lt;p&gt;모든 운영 Ingress가 Traefik으로 이전됐고 1주일 이상 안정 운영이 확인되면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 남은 nginx Ingress 확인 (0개여야 함)&lt;/span&gt;
kubectl get ingress &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token string&quot;&gt;&apos;.items[] | select(.spec.ingressClassName==&quot;nginx&quot;) | .metadata.name&apos;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Helm uninstall&lt;/span&gt;
helm uninstall ingress-nginx &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; ingress-nginx
kubectl delete namespace ingress-nginx

&lt;span class=&quot;token comment&quot;&gt;# IngressClass도 정리&lt;/span&gt;
kubectl delete ingressclass nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;DNS도 정리&lt;/strong&gt; — nginx LB의 공인 IP로 연결된 레코드가 더 이상 없는지 확인. 클라우드 LB는 리소스 과금 중이니 빠르게 제거.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;롤백 전략 — 문제 발생 시&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;3단계의 각 전환마다&lt;/strong&gt; 롤백 경로를 명확히 해두세요. 2가지 방법:&lt;/p&gt;
&lt;h3&gt;방법 A) &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt; 되돌리기 (빠름)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl patch ingress my-app &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; default &lt;span class=&quot;token parameter variable&quot;&gt;--type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;merge &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;spec&quot;:{&quot;ingressClassName&quot;:&quot;nginx&quot;}}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;단, &lt;strong&gt;DNS가 Traefik IP로 바뀐 상태&lt;/strong&gt;면 의미 없음. 아래 방법 B와 병행 필요.&lt;/p&gt;
&lt;h3&gt;방법 B) DNS 레코드 되돌리기 (근본적)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;blog.javapark.kr   A   &amp;lt;nginx-LB-IP&gt;   # 원래 값으로&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;TTL이 낮아야 빠른 롤백 가능&lt;/strong&gt; — 전환 2~3일 전에 TTL을 300초(5분) 수준으로 낮춰두세요.&lt;/p&gt;
&lt;h3&gt;데이터 플레인이 아닌 컨트롤 플레인 문제&lt;/h3&gt;
&lt;p&gt;Traefik 자체는 멀쩡한데 cert-manager 연동이 깨진 경우, &lt;strong&gt;인증서만&lt;/strong&gt; 발급 안 되는 상태가 됩니다. 이 경우:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Challenge 확인&lt;/span&gt;
kubectl get challenge &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 이전 nginx용 Secret 재사용 (같은 secretName이면 자동 복구)&lt;/span&gt;
kubectl get secret &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;tls-secret-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;인증서는 &lt;strong&gt;Secret&lt;/strong&gt;에 저장되므로, Controller만 바뀌고 Secret이 그대로면 TLS는 즉시 복구됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 체크리스트&lt;/h2&gt;
&lt;p&gt;전환 작업 직전 돌려볼 최종 체크리스트:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; nginx와 Traefik &lt;strong&gt;둘 다 Running&lt;/strong&gt;이고 LoadBalancer에 IP 할당됨&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;code class=&quot;language-text&quot;&gt;ingressClass.isDefaultClass=false&lt;/code&gt; — Traefik이 기본 Controller 아님&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ClusterIssuer &lt;code class=&quot;language-text&quot;&gt;letsencrypt-prod-traefik&lt;/code&gt; 준비됨 (&lt;code class=&quot;language-text&quot;&gt;solvers.class: traefik&lt;/code&gt;)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; DNS TTL이 &lt;strong&gt;300초 이하&lt;/strong&gt;로 낮춰져 있음 (전환 2일 전)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 대상 Ingress의 annotation 중 &lt;strong&gt;Traefik 미지원&lt;/strong&gt; 항목 확인 (예: 매우 특수한 nginx.conf snippet)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Dashboard 접근 가능 (인증 걸림)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 롤백 DNS 값 메모 (이전 nginx LB IP)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;모니터링 대시보드&lt;/strong&gt;(Grafana 등) 열어둠&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 전환 창: 트래픽 낮은 시간 + 30분 이상 여유&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 5가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;Traefik에서 Host 매칭이 안 된다&quot;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;match&lt;/code&gt; 문법 주의. 백틱(&lt;code class=&quot;language-text&quot;&gt;`&lt;/code&gt;)으로 감싸야 합니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Host(`api.javapark.kr`)   &lt;span class=&quot;token comment&quot;&gt;# ✓&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Host(&quot;api.javapark.kr&quot;)   &lt;span class=&quot;token comment&quot;&gt;# ✕ 동작 안 함&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) &quot;Middleware가 안 먹힌다&quot;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;namespace&gt;-&amp;lt;name&gt;@kubernetescrd&lt;/code&gt; 형식 지켰는지 확인. 다른 네임스페이스 Middleware를 참조하려면 전체 경로 필수.&lt;/p&gt;
&lt;h3&gt;3) &quot;cert-manager Challenge가 실패&quot;&lt;/h3&gt;
&lt;p&gt;ClusterIssuer의 &lt;code class=&quot;language-text&quot;&gt;solvers.class&lt;/code&gt;가 &lt;strong&gt;기존 nginx&lt;/strong&gt;로 남아있으면 Traefik Ingress에 대한 challenge가 실패. &lt;code class=&quot;language-text&quot;&gt;traefik&lt;/code&gt;용 ClusterIssuer 따로 쓰거나, &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 필드를 아예 빼고 공용으로.&lt;/p&gt;
&lt;h3&gt;4) &quot;전환 후 404&quot;&lt;/h3&gt;
&lt;p&gt;ingressClassName을 바꿨는데 라우팅이 안 됨 → Traefik에 해당 Ingress가 아직 등록 안 된 상태. &lt;strong&gt;Traefik Pod 재시작 or Traefik CRD 명시 확인&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;5) &quot;Dashboard가 안 열린다&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;entryPoints: [websecure]&lt;/code&gt; 설정 확인&lt;/li&gt;
&lt;li&gt;TLS secret 존재 확인&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;BasicAuth&lt;/code&gt; middleware가 잘못 설정되면 401만 뜸 — secret 형식(bcrypt) 재생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;ArgoCD와 함께 — GitOps 기반 마이그레이션&lt;/h2&gt;
&lt;p&gt;이 모든 전환을 &lt;strong&gt;Git 커밋 단위&lt;/strong&gt;로 하면 추적·롤백이 훨씬 편합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;k8s-manifests/
├── apps/
│   └── my-app/
│       └── ingress.yaml        # ingressClassName 하나 바꾸는 커밋이 전환
├── platform/
│   ├── ingress-nginx/          # 초기 상태 · 나중에 삭제 커밋
│   ├── traefik/                # 신규 추가 커밋
│   └── cert-manager/           # 그대로 유지&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ArgoCD App of Apps 패턴에서 &lt;code class=&quot;language-text&quot;&gt;traefik&lt;/code&gt; Application을 먼저 추가 → 검증 → 각 서비스 앱의 &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt;을 PR로 전환 → nginx Application 제거 PR. &lt;strong&gt;모든 전환이 Git 히스토리에 남아&lt;/strong&gt; 롤백도 &lt;code class=&quot;language-text&quot;&gt;git revert&lt;/code&gt; 한 번으로 해결.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD 편&lt;/a&gt; 패턴 그대로 재사용 가능.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. &lt;code class=&quot;language-text&quot;&gt;Ingress&lt;/code&gt;로 계속 가도 되나요, 꼭 &lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;로 전환해야 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;안 해도 됩니다.&lt;/strong&gt; Traefik은 기본 Ingress를 완벽히 지원합니다. 이미 수십 개 Ingress가 있다면 &lt;strong&gt;그대로 두고, 신규만 IngressRoute&lt;/strong&gt;로 쓰는 하이브리드가 현실적. &lt;code class=&quot;language-text&quot;&gt;IngressRoute&lt;/code&gt;는 Traefik 특화 기능(복잡한 매처·미들웨어 체인)을 쓸 때만 유리합니다.&lt;/p&gt;
&lt;h3&gt;Q. annotations가 너무 많아서 변환이 막막합니다.&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;nginx.ingress.kubernetes.io/&lt;/code&gt; annotation을 &lt;code class=&quot;language-text&quot;&gt;grep&lt;/code&gt;으로 전부 뽑은 뒤 Traefik 공식 문서의 &lt;a href=&quot;https://doc.traefik.io/traefik/routing/providers/kubernetes-ingress/&quot;&gt;annotation reference&lt;/a&gt;와 맵핑 표를 만드세요. 실무에서 자주 쓰는 건 &lt;code class=&quot;language-text&quot;&gt;force-ssl-redirect&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;proxy-body-size&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;cors&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;rewrite&lt;/code&gt; 네 개입니다.&lt;/p&gt;
&lt;h3&gt;Q. 성능 차이가 있나요?&lt;/h3&gt;
&lt;p&gt;벤치마크상 &lt;strong&gt;의미 있는 차이는 없습니다&lt;/strong&gt; (초당 수만 req 이상에서만 유의미한 차이). Traefik이 Go로 쓰여 있어 메모리·CPU 효율은 비슷하거나 소폭 유리. 실제 병목은 Controller가 아니라 백엔드 앱이 대부분.&lt;/p&gt;
&lt;h3&gt;Q. Gateway API로 바로 가는 게 낫지 않나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;관심 있으면 추천합니다.&lt;/strong&gt; Traefik은 Gateway API를 가장 먼저 native 지원한 Controller라, &lt;code class=&quot;language-text&quot;&gt;Gateway&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;HTTPRoute&lt;/code&gt; 리소스로 쓰면 향후 어떤 Controller로 바꿔도 리소스 재사용 가능. 단, 아직 일부 기능은 Ingress보다 성숙도가 낮으니 &lt;strong&gt;신규 프로젝트&lt;/strong&gt; 또는 &lt;strong&gt;POC&lt;/strong&gt;에서 먼저 시도해보세요.&lt;/p&gt;
&lt;h3&gt;Q. 마이그레이션이 얼마나 걸리나요?&lt;/h3&gt;
&lt;p&gt;서비스 10&lt;del&gt;20개 규모 기준 &lt;strong&gt;1&lt;/del&gt;2주&lt;/strong&gt;가 현실적입니다. 1단계(설치)는 1시간, 2단계(검증)는 반나절, 3단계(점진 전환)가 대부분의 시간을 먹고, 4단계(nginx 제거)는 30분. &lt;strong&gt;서두르지 말 것&lt;/strong&gt;이 가장 큰 팁.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;Controller 교체는 &quot;진입점을 건드리는 일&quot; 중 제일 조심스럽지만, &lt;strong&gt;ingressClassName 하나가 격리 스위치 역할&lt;/strong&gt;을 해주기 때문에 실제로는 감당할 만한 작업입니다. 중요한 건:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;병렬 운영 기간을 아끼지 않는다&lt;/strong&gt; — 하루 쓰고 버릴 LB IP 아까워하지 마세요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;annotation → Middleware 맵핑&lt;/strong&gt;을 미리 표로 만든다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cert-manager는 재사용&lt;/strong&gt; — ClusterIssuer만 class 분리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS TTL을 미리 낮춘다&lt;/strong&gt; — 롤백 속도의 핵심&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한 번에 하나씩&lt;/strong&gt; 전환하고 1시간 모니터링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Controller 공존 + &lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt;이 전환의 스위치&lt;/li&gt;
&lt;li&gt;기본 Ingress는 그대로 Traefik이 처리 — IngressRoute는 선택&lt;/li&gt;
&lt;li&gt;Middleware로 nginx annotation을 객체화·재사용&lt;/li&gt;
&lt;li&gt;cert-manager Secret은 Controller 교체 영향 받지 않음&lt;/li&gt;
&lt;li&gt;4단계 계획 + 각 단계별 롤백 경로 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prometheus + Grafana 모니터링 스택&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Gateway API 전환기 — 언제·어떻게 바꿀까 (Traefik native 경험 활용)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; K8S NetworkPolicy로 제로 트러스트 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 ingress-nginx에서 벗어날 생각이 있으신가요? 아니면 이미 Traefik·Istio Gateway·클라우드 관리형 ALB를 쓰고 계신지 댓글로 공유해주세요. 실전 전환 경험이 다음 편 소재가 됩니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Ingress + cert-manager 완벽 가이드 — 쿠버네티스 HTTPS 자동화와 Let's Encrypt 실전 셋업]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "3개월마다 인증서 갱신하러 로그인해서 파일 받고 Secret 교체하다가 깜빡해서 서비스 TLS 에러 뜬 적 있습니다." 솔직히 저도 그랬습니다. Let's Encrypt 무료 인증서를 수동으로 돌리면 9…]]></description><link>https://blog.javapark.kr/ingress-cert-manager-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/ingress-cert-manager-guide/</guid><pubDate>Tue, 21 Apr 2026 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;3개월마다 인증서 갱신하러 로그인해서 파일 받고 Secret 교체하다가 깜빡해서 서비스 TLS 에러 뜬 적 있습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. Let&apos;s Encrypt 무료 인증서를 수동으로 돌리면 &lt;strong&gt;90일 만기&lt;/strong&gt; 달력 알림이 울릴 때마다 배포 일 하나가 추가되고, 서비스 수가 늘어나면 &quot;어떤 도메인이 언제 만료되는지&quot; 파악 자체가 고역이 됩니다. 한 번 깜빡하면 &lt;code class=&quot;language-text&quot;&gt;NET::ERR_CERT_DATE_INVALID&lt;/code&gt; 배너가 사용자 화면에 뜨죠.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;cert-manager는 &quot;Let&apos;s Encrypt 인증서 발급·갱신을 K8S 리소스로 자동화&quot;하는 컨트롤러입니다.&lt;/strong&gt; Ingress에 annotation 한 줄이면 &lt;strong&gt;HTTPS 인증서가 자동으로 발급되고, 만료 전에 알아서 갱신&lt;/strong&gt;됩니다. 오늘은 Ingress 개념 복습부터 nginx-ingress 설치, cert-manager 셋업, Let&apos;s Encrypt 연동, 와일드카드 인증서까지 실전 순서대로 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt; · &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 완벽 가이드&lt;/a&gt; · &lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD GitOps 가이드&lt;/a&gt; · &lt;a href=&quot;/kustomize-overlay-guide/&quot;&gt;Kustomize Overlay 가이드&lt;/a&gt;부터 읽으시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Ingress 복습 — 왜 필요한가&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt;는 Pod에 접근할 경로를 제공하지만, &lt;strong&gt;도메인·경로 기반 라우팅·TLS 종단&lt;/strong&gt;은 다루지 않습니다. 이 빈자리를 채우는 게 &lt;strong&gt;Ingress&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Internet
   │
   ▼
┌───────────────────┐
│  LoadBalancer     │  ← 클라우드 LB or 노드 IP
└───────────────────┘
   │
   ▼
┌───────────────────────────────────────────────────┐
│  Ingress Controller (nginx / traefik / haproxy)   │
│   - 도메인 기반 분기                              │
│   - 경로 기반 분기                                │
│   - TLS 종단 처리                                 │
└───────────────────────────────────────────────────┘
   │                  │                   │
   ▼                  ▼                   ▼
 Service A         Service B           Service C
(api.example)   (admin.example)    (blog.example)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심 용어 구분:&lt;/p&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;&lt;strong&gt;Ingress (리소스)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;라우팅 규칙을 선언한 K8S 오브젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingress Controller&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;그 규칙을 실제로 수행하는 &lt;strong&gt;Pod&lt;/strong&gt; (nginx·traefik 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IngressClass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;여러 Controller 중 &quot;이 Ingress를 누가 처리할지&quot; 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ingress 리소스만 만들어 놓고 &lt;strong&gt;Controller가 설치되지 않았으면 아무 일도 일어나지 않습니다.&lt;/strong&gt; 처음 하는 실수 1위가 이것.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/edf0839ec226ec19e14cd4c9a5a6ed93/64756/diagram-architecture.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVR42iXR6W6jMBQFYB4hDUkTMGYJm7ENGLOanSRNm0QdTUfzY97/Sca00ifLi46O5KtsjGSL5y2ZT2xKxXsm7nYy7HB7iM9MPHj78JNZcysDCYiEGbUwbI2wAb5UKxuLqelN5XeQzGH1ETVPM10O8aixq1/courDza6QzpCMJp2gREaARxD1ulcrLzZX+XNf/9mRCba/jOKpkUWnF52eNXo+ksUuHuHw18nvRvomgeS6is9Ht1JUK9tiodLuFTVHVB/DRkcCoBZEHSQ9pINJRzMZzVTWDgbuDNzqUauHzfFUKqqd7avhMJwPVDjVxR7fQTYCNgA2GmwC6eD2T/T2derfjXyWR0mX4u7gFMrOzna43lNZ22io0yOpBaQzZG0sawfIJjObAfq+l51IaEgcg/rV5srRzuo8nQVDmJdl1bRNXok4a9Oi/0GTinFR1B3jZUArH5fSKSr3lgyfchzzJM3dSL6VERMRa8O0Q0zq5cbFZRA3OBs8UluoskKphEG5+w5XiHzR5J+DLoR/+vHTji4nfHVWFzs8++kjZJ9WsMBgMcN1lYA/qSZXNLcO8APHvx20EH4P4puDZidanHC2w8n0RxdfPHKF3mB4A/jhj7rXb9ewV2tRo2FhxD3kk1XMZj7b+Wxl8p9GI+112upErOORcL9H4sWrNhLkih40gLSAdmY6OvXFLs9ruFiccjH5mjf5CJNBzlyXQybDPmw2bqEGtWz+D8pkfwWGGtj7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/edf0839ec226ec19e14cd4c9a5a6ed93/a1b3b/diagram-architecture.webp 215w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/fc700/diagram-architecture.webp 430w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/521f7/diagram-architecture.webp 860w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/cbd37/diagram-architecture.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/edf0839ec226ec19e14cd4c9a5a6ed93/f7886/diagram-architecture.png 215w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/bc750/diagram-architecture.png 430w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/3e3fe/diagram-architecture.png 860w,
/static/edf0839ec226ec19e14cd4c9a5a6ed93/64756/diagram-architecture.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/edf0839ec226ec19e14cd4c9a5a6ed93/3e3fe/diagram-architecture.png&apos; alt=&apos;HTTPS 트래픽 흐름 + cert-manager가 끼어드는 지점 — Internet → LB → Ingress → cert-manager Secret → Pod 4계층 구조&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;HTTPS 트래픽 흐름 + cert-manager가 끼어드는 지점 — Internet → LB → Ingress → cert-manager Secret → Pod 4계층 구조&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;nginx-ingress 설치 — 10분 컷&lt;/h2&gt;
&lt;p&gt;가장 널리 쓰이는 &lt;a href=&quot;https://kubernetes.github.io/ingress-nginx/&quot;&gt;ingress-nginx&lt;/a&gt;를 Helm으로 설치합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Helm 레포 추가&lt;/span&gt;
helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

&lt;span class=&quot;token comment&quot;&gt;# 네임스페이스 + 설치 (LoadBalancer 타입 기본)&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; ingress-nginx ingress-nginx/ingress-nginx &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt; ingress-nginx --create-namespace &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;controller.service.type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;LoadBalancer

&lt;span class=&quot;token comment&quot;&gt;# 외부 IP 할당 대기&lt;/span&gt;
kubectl get svc &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; ingress-nginx &lt;span class=&quot;token parameter variable&quot;&gt;--watch&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;LoadBalancer&lt;/strong&gt;가 &lt;code class=&quot;language-text&quot;&gt;Pending&lt;/code&gt;에서 실제 IP로 바뀌면 준비 완료. 이 IP가 앞으로 모든 Ingress의 진입점입니다.&lt;/p&gt;
&lt;h3&gt;IP 확보 후 DNS 레코드 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 예시: Cloudflare·Route53·Namecheap 등에서
blog.javapark.kr   A   &amp;lt;LoadBalancer IP&gt;
api.javapark.kr    A   &amp;lt;LoadBalancer IP&gt;
*.dev.javapark.kr  A   &amp;lt;LoadBalancer IP&gt;   ← 와일드카드&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;모든 서브도메인을 같은 IP로 보내고, 분기는 Ingress가 처리.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; 관리형 K8S(EKS·GKE·AKS)는 LoadBalancer 타입이 자동으로 클라우드 LB를 프로비저닝합니다. &lt;strong&gt;bare-metal이나 로컬(minikube·kind)에서는&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;MetalLB&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;nodePort&lt;/code&gt; 방식으로 바꿔야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Ingress 기본 예제 — nginx 서비스 노출&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networking.k8s.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ingress
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ingress
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx &lt;span class=&quot;token comment&quot;&gt;# ← Controller 지정&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /
            &lt;span class=&quot;token key atrule&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Prefix
            &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
                &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 상태에선 &lt;code class=&quot;language-text&quot;&gt;http://blog.javapark.kr&lt;/code&gt;로 nginx에 접근 가능하지만 &lt;strong&gt;HTTPS는 아직 없습니다&lt;/strong&gt;. 여기서 cert-manager가 등장합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;cert-manager란? — 한 줄 정의&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://cert-manager.io/&quot;&gt;cert-manager&lt;/a&gt;는 &lt;strong&gt;K8S 클러스터 안에서 TLS 인증서 발급·갱신을 자동화&lt;/strong&gt;하는 컨트롤러입니다. CNCF Graduated 프로젝트, 사실상 K8S HTTPS 표준.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인증서 발급&lt;/strong&gt;: Let&apos;s Encrypt·ZeroSSL·HashiCorp Vault·사설 CA 모두 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 갱신&lt;/strong&gt;: 만료 30일 전에 자동으로 재발급&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret 교체&lt;/strong&gt;: 발급된 인증서를 K8S Secret으로 저장, Ingress가 자동 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심 CRD 3개:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CRD&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issuer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;특정 네임스페이스 전용 발급자 선언&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ClusterIssuer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;클러스터 전체 공용&lt;/strong&gt; 발급자 — 보통 이걸 씀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Certificate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&quot;이 도메인에 대한 인증서가 필요하다&quot;는 선언 (Ingress가 대신 만들어줌)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;cert-manager 설치&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Helm 레포 추가&lt;/span&gt;
helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; jetstack https://charts.jetstack.io
helm repo update

&lt;span class=&quot;token comment&quot;&gt;# 설치 (CRD 자동 설치 옵션)&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; cert-manager jetstack/cert-manager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt; cert-manager --create-namespace &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;installCRDs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true

&lt;span class=&quot;token comment&quot;&gt;# 확인&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; cert-manager&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;세 개의 Pod(&lt;code class=&quot;language-text&quot;&gt;cert-manager&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;cert-manager-webhook&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;cert-manager-cainjector&lt;/code&gt;)가 모두 &lt;code class=&quot;language-text&quot;&gt;Running&lt;/code&gt;이면 준비 완료.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ClusterIssuer 만들기 — Let&apos;s Encrypt&lt;/h2&gt;
&lt;h3&gt;1) Staging 먼저 (테스트용)&lt;/h3&gt;
&lt;p&gt;실전 발급 전 &lt;strong&gt;staging&lt;/strong&gt;으로 먼저 테스트하세요. 실수로 Let&apos;s Encrypt &lt;strong&gt;rate limit&lt;/strong&gt;(도메인당 주간 발급 제한)에 걸리면 일주일 동안 발급 불가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ClusterIssuer
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;staging
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;acme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//acme&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;staging&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v02.api.letsencrypt.org/directory
    &lt;span class=&quot;token key atrule&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; you@example.com
    &lt;span class=&quot;token key atrule&quot;&gt;privateKeySecretRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;staging&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key
    &lt;span class=&quot;token key atrule&quot;&gt;solvers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;http01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) Production (실제 발급용)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ClusterIssuer
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;acme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//acme&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v02.api.letsencrypt.org/directory
    &lt;span class=&quot;token key atrule&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; you@example.com
    &lt;span class=&quot;token key atrule&quot;&gt;privateKeySecretRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key
    &lt;span class=&quot;token key atrule&quot;&gt;solvers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;http01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;차이는 &lt;code class=&quot;language-text&quot;&gt;server&lt;/code&gt; URL만. 배포:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; clusterissuer-staging.yaml
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; clusterissuer-prod.yaml

kubectl get clusterissuer
&lt;span class=&quot;token comment&quot;&gt;# NAME                  READY   AGE&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# letsencrypt-staging   True    10s&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# letsencrypt-prod      True    10s&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;실전 — Ingress에 HTTPS 자동 발급&lt;/h2&gt;
&lt;p&gt;Ingress에 &lt;strong&gt;annotation + tls 섹션만&lt;/strong&gt; 추가하면 끝.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networking.k8s.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ingress
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ingress
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod &lt;span class=&quot;token comment&quot;&gt;# 이 한 줄&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; blog.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls &lt;span class=&quot;token comment&quot;&gt;# cert-manager가 여기에 저장&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /
            &lt;span class=&quot;token key atrule&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Prefix
            &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
                &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/c2afcf812dc4f38d51cd241edb913209/64756/diagram-cert-flow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACPUlEQVR42hWS6Y6bQBCEeYFV1uY0NhgwxzAn9+HBw2BsHEdRlER5/2fJrNQq9Z+vq0pq7fNA9slkXbogQQEsPVQGbGDDVo5vUN7J+M7rh33pvHT0wdUD3MtGNcdkcJNe2x3ZDkg75xnrUzpGeMjruef3YXpW3dIOP1j9igE/kduRTEGhVDj55MLpC957pVX9dNjzTFZK/1L2i5BVjKxvGSPNSDZO312xRuvbn7a4l97825X//NsfJxm0vV8YcDGhNDMegzWFW5iKoqyKqoakokhW5Ily6TfLqV1sNtnF/VC/LLZaUavpfqlnwsxV8jlIZJTdL0AiUmLWINrgfKboTqB02XSobk7BLTJbRJp4NqNGM861kc8mXBQcpksEniFYEK1x0SHaYigVjIA4MO6UN5tyEwuTLAaazaDWjKBRmS282FCG2RrlWwTumLak7DDrlDPOlxwIh12dklt0NJAwkdShMIJKU9EtJG2yHvA9BI8LfMXwQYqWVQNR5kCowhAqeLRLbrJxj266gnOh+n7BLl4OigciwdsFPs6pQGwApE9AhbFMsimKBxVb2eqoM+FkpHwX93uv+ILPePHVpbCL4epH0/HcpXkXpo0XIIhEnHL3VJyLmwP6j5B5VLrp9eNU7E5Mc5Ph1G0ef1ymVzy+oul7yLeMr1m/RNUtbdesXY/kqjx13O/ydp+1n2nzLa53LtGO6eBR4RXiXMqgkn41q91jQr2UC7mLrgc0mrA3QWdkrR43elTtgkLp/kj/A9FWfbpFBBBSAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/c2afcf812dc4f38d51cd241edb913209/a1b3b/diagram-cert-flow.webp 215w,
/static/c2afcf812dc4f38d51cd241edb913209/fc700/diagram-cert-flow.webp 430w,
/static/c2afcf812dc4f38d51cd241edb913209/521f7/diagram-cert-flow.webp 860w,
/static/c2afcf812dc4f38d51cd241edb913209/cbd37/diagram-cert-flow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/c2afcf812dc4f38d51cd241edb913209/f7886/diagram-cert-flow.png 215w,
/static/c2afcf812dc4f38d51cd241edb913209/bc750/diagram-cert-flow.png 430w,
/static/c2afcf812dc4f38d51cd241edb913209/3e3fe/diagram-cert-flow.png 860w,
/static/c2afcf812dc4f38d51cd241edb913209/64756/diagram-cert-flow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/c2afcf812dc4f38d51cd241edb913209/3e3fe/diagram-cert-flow.png&apos; alt=&apos;Let&amp;#39;s Encrypt HTTP-01 인증서 발급 자동화 6단계 플로우 — Ingress 생성부터 Secret 저장까지&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Let&amp;#39;s Encrypt HTTP-01 인증서 발급 자동화 6단계 플로우 — Ingress 생성부터 Secret 저장까지&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;적용:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; nginx-ingress.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;진행 상황 추적:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1. Certificate 리소스 자동 생성 확인&lt;/span&gt;
kubectl get certificate
&lt;span class=&quot;token comment&quot;&gt;# NAME       READY   SECRET     AGE&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# blog-tls   False   blog-tls   5s        ← 발급 중&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 2. Order + Challenge 확인&lt;/span&gt;
kubectl get order
kubectl get challenge

&lt;span class=&quot;token comment&quot;&gt;# 3. 성공 시&lt;/span&gt;
kubectl get certificate
&lt;span class=&quot;token comment&quot;&gt;# NAME       READY   SECRET     AGE&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# blog-tls   True    blog-tls   45s&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;보통 &lt;strong&gt;30초~1분&lt;/strong&gt; 내에 &lt;code class=&quot;language-text&quot;&gt;READY: True&lt;/code&gt;. 이제 &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr&lt;/code&gt; 접속하면 &lt;strong&gt;자동 발급된 인증서&lt;/strong&gt;로 연결됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; Certificate가 오랫동안 &lt;code class=&quot;language-text&quot;&gt;False&lt;/code&gt;면 &lt;code class=&quot;language-text&quot;&gt;kubectl describe certificate blog-tls&lt;/code&gt;로 events 확인. 80%는 &lt;strong&gt;DNS가 아직 전파 안 됨&lt;/strong&gt; 또는 &lt;strong&gt;80번 포트가 인바운드 차단&lt;/strong&gt;된 경우.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;HTTP-01 vs DNS-01 챌린지&lt;/h2&gt;
&lt;p&gt;Let&apos;s Encrypt가 &quot;이 도메인이 정말 네 것이냐&quot;를 검증하는 방식이 두 가지.&lt;/p&gt;
&lt;h3&gt;HTTP-01 (기본, 가장 쉬움)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;cert-manager → 특정 경로에 토큰 파일 임시 배치
Let&apos;s Encrypt → http://&amp;lt;domain&gt;/.well-known/acme-challenge/&amp;lt;token&gt; 접근 → 확인&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도메인이 공인 IP로 연결되어 있어야 함&lt;/li&gt;
&lt;li&gt;80번 포트가 공개되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;불가능한 경우:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;내부망(VPN 뒤) 도메인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;와일드카드(&lt;code class=&quot;language-text&quot;&gt;*.example.com&lt;/code&gt;) 인증서&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DNS-01 (와일드카드·내부 도메인용)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;cert-manager → DNS 공급자 API로 TXT 레코드 등록
Let&apos;s Encrypt → _acme-challenge.&amp;lt;domain&gt; TXT 레코드 조회 → 확인&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS 공급자가 API 지원 (Cloudflare·Route53·Google Cloud DNS 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt; 와일드카드 가능, 내부망 가능.&lt;/p&gt;
&lt;h3&gt;DNS-01 예시 (Cloudflare)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Secret
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cloudflare&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;token
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager
&lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Opaque
&lt;span class=&quot;token key atrule&quot;&gt;stringData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;api-token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;your&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cloudflare&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ClusterIssuer
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dns
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;acme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//acme&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v02.api.letsencrypt.org/directory
    &lt;span class=&quot;token key atrule&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; you@example.com
    &lt;span class=&quot;token key atrule&quot;&gt;privateKeySecretRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dns&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key
    &lt;span class=&quot;token key atrule&quot;&gt;solvers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;dns01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;cloudflare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;apiTokenSecretRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cloudflare&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;token
              &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;token&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;와일드카드 인증서 — 서브도메인 통합&lt;/h2&gt;
&lt;p&gt;여러 서브도메인(&lt;code class=&quot;language-text&quot;&gt;api.dev.javapark.kr&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;admin.dev.javapark.kr&lt;/code&gt; …)을 &lt;strong&gt;한 인증서&lt;/strong&gt;로 커버.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networking.k8s.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ingress
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wildcard&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ingress
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dns &lt;span class=&quot;token comment&quot;&gt;# DNS-01 필수&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*.dev.javapark.kr&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wildcard&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api.dev.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /
            &lt;span class=&quot;token key atrule&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Prefix
            &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
                &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; admin.dev.javapark.kr
      &lt;span class=&quot;token key atrule&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /
            &lt;span class=&quot;token key atrule&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Prefix
            &lt;span class=&quot;token key atrule&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; admin&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
                &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;와일드카드 하나 발급해두면 &lt;strong&gt;새 서브도메인 추가 시 인증서 재발급 없이&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;rules&lt;/code&gt;만 늘리면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;인증서 자동 갱신&lt;/h2&gt;
&lt;p&gt;cert-manager는 &lt;strong&gt;기본적으로 만료 30일 전에 자동 갱신&lt;/strong&gt;합니다. 별도 설정 불필요.&lt;/p&gt;
&lt;p&gt;직접 확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get certificate
&lt;span class=&quot;token comment&quot;&gt;# NAME              READY   SECRET            AGE   RENEWAL&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# blog-tls          True    blog-tls          45d   2026-05-20&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 만료 시점 확인&lt;/span&gt;
kubectl describe certificate blog-tls &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Not After&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;수동으로 즉시 갱신 트리거:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl cert-manager renew blog-tls
&lt;span class=&quot;token comment&quot;&gt;# 또는&lt;/span&gt;
kubectl delete secret blog-tls  &lt;span class=&quot;token comment&quot;&gt;# Secret 지우면 재발급&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;자주 쓰는 부가 annotation&lt;/h2&gt;
&lt;p&gt;nginx-ingress 기준으로 실전에서 많이 쓰는 annotation들.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# cert-manager&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; letsencrypt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod

    &lt;span class=&quot;token comment&quot;&gt;# HTTPS 강제 (HTTP로 오면 301 → HTTPS)&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/force-ssl-redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 큰 파일 업로드 (기본 1MB → 50MB)&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;50m&quot;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 타임아웃&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/proxy-read-timeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;60&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/proxy-send-timeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;60&quot;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# CORS&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/enable-cors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/cors-allow-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://app.example.com&quot;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 라우팅 재작성&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /$2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 annotation들이 &lt;strong&gt;ingress-nginx 특화&lt;/strong&gt;라는 점 주의. traefik·istio 쓰면 문법이 다릅니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 5가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;Certificate가 &lt;code class=&quot;language-text&quot;&gt;READY: False&lt;/code&gt;에서 안 넘어간다&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl describe certificate &amp;lt;name&gt;&lt;/code&gt;&lt;/strong&gt; → Events 확인&lt;/li&gt;
&lt;li&gt;80%는 &lt;strong&gt;DNS 전파 안 됨&lt;/strong&gt; 또는 &lt;strong&gt;80번 포트 차단&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;staging으로 먼저 테스트했는지 확인 (rate limit 원인 배제)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2) &quot;Too many failed authorizations&quot;&lt;/h3&gt;
&lt;p&gt;Let&apos;s Encrypt rate limit 위반. 1주일 대기 또는 &lt;strong&gt;staging&lt;/strong&gt;으로 전환해 검증 후 prod로 교체.&lt;/p&gt;
&lt;h3&gt;3) &quot;Mixed content 에러&quot;&lt;/h3&gt;
&lt;p&gt;HTTPS 페이지 안에서 HTTP 리소스 로드. &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;force-ssl-redirect: &quot;true&quot;&lt;/code&gt;&lt;/strong&gt; + 애플리케이션 레벨에서 절대 URL을 https로 통일.&lt;/p&gt;
&lt;h3&gt;4) &quot;Ingress는 떴는데 404&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ingressClassName&lt;/code&gt; 지정 누락 → Controller가 Ingress를 &lt;strong&gt;무시&lt;/strong&gt;함&lt;/li&gt;
&lt;li&gt;path·pathType 불일치 (&lt;code class=&quot;language-text&quot;&gt;Prefix&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;Exact&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Service name·port 오타&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5) &quot;갱신이 일어나지 않음&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl describe certificate&lt;/code&gt; → 상태 확인&lt;/li&gt;
&lt;li&gt;cert-manager Pod 로그: &lt;code class=&quot;language-text&quot;&gt;kubectl logs -n cert-manager deploy/cert-manager&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;ArgoCD와 함께 쓰기&lt;/h2&gt;
&lt;p&gt;cert-manager·ingress-nginx 같은 &lt;strong&gt;플랫폼 컴포넌트&lt;/strong&gt;는 ArgoCD의 별도 Application으로 선언해두면 인프라 재현이 자동화됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argoproj.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Application
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argocd
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; platform
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//charts.jetstack.io
    &lt;span class=&quot;token key atrule&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager
    &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1.16.0
    &lt;span class=&quot;token key atrule&quot;&gt;helm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
        installCRDs: true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//kubernetes.default.svc
    &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cert&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manager
  &lt;span class=&quot;token key atrule&quot;&gt;syncPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;automated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;prune&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;selfHeal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;syncOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ClusterIssuer는 별도 Git 레포에 YAML로 두고, &lt;strong&gt;App of Apps&lt;/strong&gt; 패턴으로 같이 관리. &lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD 편&lt;/a&gt;의 패턴 그대로 재사용 가능.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. ingress-nginx 말고 다른 Controller를 써야 할 때는?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traefik&lt;/strong&gt;: 자동 서비스 디스커버리가 매력적. 가벼운 setup에 적합.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Istio / Envoy Gateway&lt;/strong&gt;: 서비스 메시가 이미 있으면 그 안에서 처리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS ALB Ingress Controller&lt;/strong&gt;: AWS 환경에서 &lt;strong&gt;ALB로 직접&lt;/strong&gt; 노출하고 싶을 때.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt;: 퍼블릭 IP 노출 없이 Ingress 기능 흉내.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;입문은 ingress-nginx.&lt;/strong&gt; 생태계·문서·annotation 레퍼런스가 가장 풍부.&lt;/p&gt;
&lt;h3&gt;Q. cert-manager와 Gateway API는 어떻게 다르나요?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://gateway-api.sigs.k8s.io/&quot;&gt;Gateway API&lt;/a&gt;는 Ingress의 &lt;strong&gt;차세대 표준&lt;/strong&gt;(K8S SIG). cert-manager는 현재 Gateway API와도 &lt;strong&gt;통합&lt;/strong&gt; 지원 (&lt;code class=&quot;language-text&quot;&gt;gateway&lt;/code&gt; solver). 당분간 Ingress가 주력이지만, 2026~2027년경 전환기가 올 수 있습니다. 신규 프로젝트에서 Gateway API로 시작해보는 것도 좋은 선택.&lt;/p&gt;
&lt;h3&gt;Q. Let&apos;s Encrypt가 중단되면 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;cert-manager는 &lt;strong&gt;여러 Issuer를 병행 운영&lt;/strong&gt; 가능. ClusterIssuer를 &lt;strong&gt;ZeroSSL·Google Trust Services·사설 CA&lt;/strong&gt; 등으로 바꾸기만 하면 됨. 인증서 프로바이더 락인 없음.&lt;/p&gt;
&lt;h3&gt;Q. 사내 CA로도 가능한가요?&lt;/h3&gt;
&lt;p&gt;네. &lt;code class=&quot;language-text&quot;&gt;ClusterIssuer&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;ca&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;vault&lt;/code&gt; solver를 쓰면 사내 PKI·HashiCorp Vault와 연동되어 &lt;strong&gt;내부 도메인 인증서도 자동화&lt;/strong&gt;됩니다.&lt;/p&gt;
&lt;h3&gt;Q. 한 Secret을 여러 네임스페이스에서 공유하려면?&lt;/h3&gt;
&lt;p&gt;cert-manager의 &lt;code class=&quot;language-text&quot;&gt;kubectl-cert-manager&lt;/code&gt; 플러그인 또는 &lt;a href=&quot;https://github.com/emberstack/kubernetes-reflector&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;reflector&lt;/code&gt;&lt;/a&gt; 컨트롤러로 Secret 자동 복제. 와일드카드 인증서 한 개를 전사에 뿌릴 때 자주 씀.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;HTTPS가 수동 관리 업무에서 &lt;strong&gt;인프라 기본값&lt;/strong&gt;으로 내려옵니다. 한 번 셋업하면 &lt;strong&gt;인증서를 다시 신경 쓸 일이 없습니다&lt;/strong&gt; — 이 경험 자체가 가치의 핵심.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ingress는 &lt;strong&gt;규칙&lt;/strong&gt;, Ingress Controller는 &lt;strong&gt;수행자&lt;/strong&gt; — 둘 다 필요&lt;/li&gt;
&lt;li&gt;cert-manager = &lt;strong&gt;Issuer·ClusterIssuer·Certificate&lt;/strong&gt; 3개 CRD&lt;/li&gt;
&lt;li&gt;Let&apos;s Encrypt는 &lt;strong&gt;staging으로 먼저 테스트&lt;/strong&gt;, prod는 확인 후&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP-01&lt;/strong&gt;은 간단, &lt;strong&gt;DNS-01&lt;/strong&gt;은 와일드카드·내부망 대응&lt;/li&gt;
&lt;li&gt;Ingress에 &lt;strong&gt;annotation + tls 섹션&lt;/strong&gt;이면 자동 발급·갱신&lt;/li&gt;
&lt;li&gt;ArgoCD와 묶으면 인프라 재현까지 GitOps화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prometheus + Grafana 모니터링 스택&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Gateway API 전환기 — 언제·어떻게 바꿀까&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; K8S 네트워크 정책(NetworkPolicy)과 보안 레이어링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분의 클러스터에서는 인증서 갱신을 어떻게 관리하고 계신가요? 수동·스크립트·cert-manager — 어느 단계에 계신지 댓글로 공유해주세요.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kustomize 완벽 가이드 — 템플릿 없이 YAML만으로 환경별 배포 관리하기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Helm 템플릿 문법 괄호 속에서 버벅이는 게 지친다. 그냥 YAML…]]></description><link>https://blog.javapark.kr/kustomize-overlay-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/kustomize-overlay-guide/</guid><pubDate>Tue, 21 Apr 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Helm 템플릿 문법 괄호 속에서 버벅이는 게 지친다. 그냥 YAML 몇 줄만 바꿔서 환경별로 배포하고 싶다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. &lt;code class=&quot;language-text&quot;&gt;{{- if .Values.ingress.enabled }}&lt;/code&gt; 같은 템플릿 안에서 들여쓰기 한 칸 틀리면 렌더링 전체가 깨지고, 디버깅은 &lt;code class=&quot;language-text&quot;&gt;helm template&lt;/code&gt;을 수십 번 돌려보며 YAML을 다시 맞추는 작업이 됩니다. 소규모 팀·단일 앱에서는 이게 &lt;strong&gt;배보다 배꼽이 큰&lt;/strong&gt; 느낌이 들기 시작합니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Kustomize는 템플릿 문법 없이 &quot;원본 YAML을 그대로 두고, 환경별 변경점만 patch로 쌓아 올리는&quot; 매니페스트 관리 도구입니다.&lt;/strong&gt; Go 템플릿을 배울 필요가 없고, &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt;에 &lt;strong&gt;이미 내장&lt;/strong&gt;되어 있어 별도 설치가 필요 없습니다. &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 편&lt;/a&gt;에서 예고한 대로, 오늘은 이 kustomize의 구조·변환기·patch 전략·Helm과의 비교·조합 패턴·ArgoCD 연동까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt; · &lt;a href=&quot;/kubectl-essential-commands/&quot;&gt;kubectl 실전 명령어&lt;/a&gt; · &lt;a href=&quot;/k9s-essential-guide/&quot;&gt;k9s 완벽 가이드&lt;/a&gt; · &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 완벽 가이드&lt;/a&gt; · &lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD GitOps 가이드&lt;/a&gt;부터 읽으시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Kustomize란? — 한 줄 정의&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kustomize.io/&quot;&gt;Kustomize&lt;/a&gt;는 &lt;strong&gt;K8S 매니페스트를 &quot;원본(base) + 변경점(overlay)&quot;으로 분리해서 관리&lt;/strong&gt;하는 선언적 변환 도구입니다. K8S SIG(Special Interest Group)가 유지보수하며, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 1.14+부터 내장&lt;/strong&gt;되어 있습니다.&lt;/p&gt;
&lt;p&gt;핵심 철학:&lt;/p&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;&lt;strong&gt;Template-free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go 템플릿·변수 치환 없음. 순수 YAML만 다룸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Composition-based&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;변환·patch를 &lt;strong&gt;쌓아 올려&lt;/strong&gt; 최종 YAML 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Declarative all the way&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;kustomization.yaml&lt;/code&gt; 파일 자체가 선언형 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reusable bases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;공통 &lt;code class=&quot;language-text&quot;&gt;base&lt;/code&gt;를 여러 환경 overlay가 재사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;비유: &lt;strong&gt;Git의 diff/patch 모델&lt;/strong&gt;을 K8S YAML에 적용한 것. 원본을 수정하지 않고 &lt;strong&gt;patch 파일을 얹는&lt;/strong&gt; 방식이라 base가 항상 &quot;순수&quot;하게 유지됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3대 핵심 개념 — Base, Overlay, Kustomization&lt;/h2&gt;
&lt;p&gt;Kustomize의 모든 것은 이 세 단어로 정리됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/aefbf4ba38a4fc5e3c881e8843b00182/64756/diagram-structure.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACNklEQVR42h2SaW6kMBBGucEkNItZzGaD2YzB2KzuLemeSdKRcoW5/ynGPdJT/Sk9VZW+Mn5BZlbnQ6VMIk2yBc2x6E6NuDfjrZW/G3kf9sewPULdKhWs9pBo1iBf/Xw2XiL20p9fqTr0m9kowG7JcKvkn2r+bNcH3R7d/k2Xr7ReSCuzesmqOSmnhEgfT8ZLzPL7d7LfX9vZRAwvP/3nX4coW1Mot7p4zcVGczeqn5/vdtjKVmiKevSz8SkjvmFxssh8yMdofM/5u51K0FxT8RWQBRAVlHvVzct+zojwY6YJU+Zl3DBjZmEe05NLZhPzoL/GlTrAAdTXfPzwMulks0/2suGklz7ibkBtv/UiCtLBMJPeZid3eLfbDdSzW+8m5IdodMuzjRY7lg5agvJYtaLpZJhyEFIAqR93btIbVsbBePSYctnmtSuolJXOdjo55OzgzUqkixev2PRkxgREwok6R0+G1Il7w0lHhy7aBP3u1SumH4heYblH9fkZD1EAry6aMzKQmic5hxmDSQdT5kAtI/HfVO6gglZF1ZudCieVLlp01SvY2eyiKSo4rjguNaMGFfwpu1gAtmoZDCpsFSQXfbAVy0MkTDi+hvwQCzsRYcZwwVA+oJwjzDPUO2FvADy5dAXd5rHd7xQcLvF2D+ZrMr2lyy2Rbwm/wu6U9ae4XUDO/Vx4uQBYWOFg+MUS1Pord9geIT3C7hgNp4CpkO4h1YfsfrV45RMnn61MWumk0RFYIf8HdVl1rpMjO5YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/aefbf4ba38a4fc5e3c881e8843b00182/a1b3b/diagram-structure.webp 215w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/fc700/diagram-structure.webp 430w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/521f7/diagram-structure.webp 860w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/cbd37/diagram-structure.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/aefbf4ba38a4fc5e3c881e8843b00182/f7886/diagram-structure.png 215w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/bc750/diagram-structure.png 430w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/3e3fe/diagram-structure.png 860w,
/static/aefbf4ba38a4fc5e3c881e8843b00182/64756/diagram-structure.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/aefbf4ba38a4fc5e3c881e8843b00182/3e3fe/diagram-structure.png&apos; alt=&apos;Kustomize Base + Overlay 구조도 — base 원본은 그대로 두고 overlay가 patch로 환경별 결과 생성&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Kustomize Base + Overlay 구조도 — base 원본은 그대로 두고 overlay가 patch로 환경별 결과 생성&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&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;&lt;strong&gt;Base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;공통 매니페스트 묶음 (변경 없는 원본 YAML 모음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overlay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;환경별 변경점 묶음 (base를 참조하고 그 위에 patch 적용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kustomization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;kustomization.yaml&lt;/code&gt; — base든 overlay든 &lt;strong&gt;각 디렉토리의 설계도&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;전형적인 디렉토리 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;my-app/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   └── kustomization.yaml       ← base 설계도
└── overlays/
    ├── dev/
    │   ├── patch.yaml
    │   └── kustomization.yaml   ← base를 참조 + dev patch
    ├── staging/
    │   ├── patch.yaml
    │   └── kustomization.yaml
    └── prod/
        ├── patch.yaml
        └── kustomization.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;핵심 규칙&lt;/strong&gt;: base는 &lt;strong&gt;절대&lt;/strong&gt; 환경별 정보를 가지지 않습니다. &lt;code class=&quot;language-text&quot;&gt;replicas: 1&lt;/code&gt; 같은 기본값도 overlay가 덮어씁니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치 — &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt;만 있으면 끝&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl apply -k &amp;lt;dir&gt;&lt;/code&gt; 로 바로 사용 가능. 별도 설치 불필요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 현재 kubectl 버전 확인 (1.14+ 권장)&lt;/span&gt;
kubectl version &lt;span class=&quot;token parameter variable&quot;&gt;--client&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# kustomize 렌더링만 (apply 없이)&lt;/span&gt;
kubectl kustomize overlays/prod

&lt;span class=&quot;token comment&quot;&gt;# 렌더링 + 적용&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-k&lt;/span&gt; overlays/prod&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;더 최신 기능(Helm 통합·Component 등)이 필요하면 &lt;strong&gt;standalone CLI&lt;/strong&gt; 설치:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# macOS&lt;/span&gt;
brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; kustomize

&lt;span class=&quot;token comment&quot;&gt;# Linux / Windows&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 확인&lt;/span&gt;
kustomize version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 내장 버전은 보통 최신보다 뒤처집니다. 신기능 쓰려면 standalone &lt;code class=&quot;language-text&quot;&gt;kustomize&lt;/code&gt; CLI 권장.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;실전 10분 — nginx를 base + 2개 overlay로 분리&lt;/h2&gt;
&lt;p&gt;Helm 편에서 만들었던 nginx 앱을, 이번엔 kustomize로 똑같이 구성해보겠습니다.&lt;/p&gt;
&lt;h3&gt;1단계: Base 만들기&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;base/deployment.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
          &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.27&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;base/service.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Service
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;base/kustomization.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize.config.k8s.io/v1beta1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Kustomization

&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; deployment.yaml
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; service.yaml

&lt;span class=&quot;token key atrule&quot;&gt;commonLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: Dev overlay&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;overlays/dev/kustomization.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize.config.k8s.io/v1beta1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Kustomization

&lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev
&lt;span class=&quot;token key atrule&quot;&gt;namePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ../../base

&lt;span class=&quot;token key atrule&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;newTag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.27&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alpine &lt;span class=&quot;token comment&quot;&gt;# 더 가벼운 이미지&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;commonAnnotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; development&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3단계: Prod overlay&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;overlays/prod/kustomization.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize.config.k8s.io/v1beta1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Kustomization

&lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
&lt;span class=&quot;token key atrule&quot;&gt;namePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ../../base

&lt;span class=&quot;token key atrule&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;newTag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.27&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 안정 태그&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; resources&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;patch.yaml
    &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;overlays/prod/resources-patch.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
          &lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 500m
              &lt;span class=&quot;token key atrule&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 512Mi
            &lt;span class=&quot;token key atrule&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000m
              &lt;span class=&quot;token key atrule&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1Gi&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4단계: 렌더링 · 배포&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 렌더링해서 눈으로 확인&lt;/span&gt;
kubectl kustomize overlays/prod

&lt;span class=&quot;token comment&quot;&gt;# 바로 배포&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-k&lt;/span&gt; overlays/prod

&lt;span class=&quot;token comment&quot;&gt;# 제거&lt;/span&gt;
kubectl delete &lt;span class=&quot;token parameter variable&quot;&gt;-k&lt;/span&gt; overlays/prod&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;한 번도 템플릿 문법을 쓰지 않았고&lt;/strong&gt;, 모든 파일이 그 자체로 &lt;strong&gt;valid한 K8S YAML&lt;/strong&gt;입니다. 에디터 자동완성·검증이 전부 그대로 동작합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;주요 변환기(Transformers) — 자주 쓰는 6가지&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;kustomization.yaml&lt;/code&gt;에서 쓸 수 있는 &quot;내장 조작 기능&quot;들입니다.&lt;/p&gt;
&lt;h3&gt;1) &lt;code class=&quot;language-text&quot;&gt;commonLabels&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;commonAnnotations&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;모든 리소스에 공통 라벨·어노테이션을 붙입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;commonLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;app.kubernetes.io/managed-by&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize
  &lt;span class=&quot;token key atrule&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; platform

&lt;span class=&quot;token key atrule&quot;&gt;commonAnnotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ops@example.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) &lt;code class=&quot;language-text&quot;&gt;namePrefix&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;nameSuffix&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;리소스 이름 앞·뒤에 접두/접미사. 같은 클러스터에 여러 환경 띄울 때 필수.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;namePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;nameSuffix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v2
&lt;span class=&quot;token comment&quot;&gt;# nginx → prod-nginx-v2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3) &lt;code class=&quot;language-text&quot;&gt;namespace&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;모든 리소스의 네임스페이스를 일괄 지정.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4) &lt;code class=&quot;language-text&quot;&gt;images&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;이미지 태그·레지스트리를 교체. 배포 파이프라인에서 &lt;strong&gt;가장 많이 쓰는&lt;/strong&gt; 기능.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;newName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; myregistry.io/nginx &lt;span class=&quot;token comment&quot;&gt;# 레지스트리 교체&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;newTag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1.2.4 &lt;span class=&quot;token comment&quot;&gt;# 태그 교체&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CI에서 이 &lt;code class=&quot;language-text&quot;&gt;newTag&lt;/code&gt;만 &lt;code class=&quot;language-text&quot;&gt;sed&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;yq&lt;/code&gt;로 수정하면 CD가 이어지는 패턴이 흔합니다.&lt;/p&gt;
&lt;h3&gt;5) &lt;code class=&quot;language-text&quot;&gt;replicas&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;replica 개수 오버라이드.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6) &lt;code class=&quot;language-text&quot;&gt;configMapGenerator&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;secretGenerator&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;ConfigMap·Secret을 &lt;strong&gt;파일 또는 literal에서 자동 생성&lt;/strong&gt;. &lt;strong&gt;해시 suffix&lt;/strong&gt;가 자동으로 붙어 &lt;strong&gt;변경 시 Pod 재시작&lt;/strong&gt;을 자연스럽게 유도.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;configMapGenerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config
    &lt;span class=&quot;token key atrule&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; configs/app.conf
    &lt;span class=&quot;token key atrule&quot;&gt;literals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; LOG_LEVEL=info

&lt;span class=&quot;token key atrule&quot;&gt;secretGenerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;creds
    &lt;span class=&quot;token key atrule&quot;&gt;envs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; .env.prod&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 기능 하나만으로도 &quot;ConfigMap 바꾸고 Pod가 안 새로고침되는&quot; 고질적 이슈가 사라집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Patch 전략 — Strategic Merge vs JSON 6902&lt;/h2&gt;
&lt;p&gt;복잡한 변경은 patch 파일로 처리합니다. 두 가지 방식.&lt;/p&gt;
&lt;h3&gt;Strategic Merge Patch (추천, 대부분의 경우)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;YAML 부분 덮어쓰기&lt;/strong&gt;. K8S 리소스의 &quot;전략적 병합&quot; 규칙을 따름 (배열은 &lt;code class=&quot;language-text&quot;&gt;name&lt;/code&gt; 기반 병합 등).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# overlays/prod/kustomization.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; patch&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;resources.yaml
    &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# overlays/prod/patch-resources.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
          &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; NODE_ENV
              &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
          &lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000m&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt; YAML 그대로 읽힘. 리뷰·머지 쉬움.
&lt;strong&gt;단점:&lt;/strong&gt; 배열 요소를 &quot;이름 없이&quot; 추가할 때 제약 있음.&lt;/p&gt;
&lt;h3&gt;JSON 6902 Patch (정교한 제어)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JSON Pointer 경로로 명령형 변경&lt;/strong&gt;. add/remove/replace 연산.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
      - op: replace
        path: /spec/replicas
        value: 5
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          name: LOG_LEVEL
          value: debug&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;언제 쓰나:&lt;/strong&gt; 배열 요소 삭제, 정확한 인덱스 지정, Strategic Merge로 표현 불가능한 변경.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 기준:&lt;/strong&gt; 평소엔 Strategic Merge, &lt;strong&gt;&quot;이 한 줄을 정확히 지워야 한다&quot; 같은 경우에만&lt;/strong&gt; JSON 6902. 섞어 쓸 수 있음.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Components — 재사용 가능한 기능 묶음&lt;/h2&gt;
&lt;p&gt;여러 overlay에서 &lt;strong&gt;공통으로 얹고 싶은 변경점&lt;/strong&gt;(예: 모니터링 사이드카, 보안 정책)을 Component로 추출.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;components/
└── monitoring/
    ├── sidecar-patch.yaml
    └── kustomization.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# components/monitoring/kustomization.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize.config.k8s.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Component

&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sidecar&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;patch.yaml
    &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;overlay에서 쓰기:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# overlays/prod/kustomization.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ../../base

&lt;span class=&quot;token key atrule&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ../../components/monitoring
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ../../components/istio&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;injection&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Helm의 subchart보다 훨씬 가볍고&lt;/strong&gt; 선언적입니다. &quot;기능 단위 레이어&quot;를 섞어 쓰는 감각.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Helm vs Kustomize — 언제 뭘 쓰나?&lt;/h2&gt;
&lt;p&gt;가장 자주 받는 질문. 실전 기준으로 정리.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/ba8f80e4182d2ecfd17af885a971adb8/64756/diagram-vs-helm.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMUlEQVR42j3S246bMBAGYN4gQA5g7GBONmBjMBgTEsimSTbbdrVVt5WqXlZqb/r+T1BzU+nTaCT7t8aWLQfWm6SnRY35DNhTzKeqv5fdjTZX3r9U+gHJAOmI6LgvTmFxMhXlIyTGwXKhwHlbVxWVcyzPrJ36402N90ZfRX8rh89BPsFiQvwJ8YupsDwvihlkg7WB7Hkq3m684qzIeVHWJK+zvKasS0kDxm/R699w+ILkA7UvqHkE9TNc3L10sLaoMvuE1DlTKZEJkbFB25h2xr65R/otbF8gv/jF2S9mL5939LSl0y7prQ0SMO48rHaB8KFYB9wF3F0qg4mE+REk2g1qF9YOFIYdCBtUbtjuYmXtwtpHIkwHiFu0b33YuGYNMNtjKO28VO8itSPaRiZWmdjCZ85ebiNleViiuAkTFUQSYhngBuAa4MYP65B0iC9vg9jZI+M67texNtWNlBvrTdhafiSjXMNMeXvhYeFjYZIgMkc0IVGwOPn04OLWQfI/21wh7NwlHLf7VIG4dUBprrpeVIYDeJBIQA5eqlawWvncBosVMD2zYWMmt4Kky1hPmM6rgXBtUG6agZR9WiqPHmE5b1K9iXoXKwcrG3f2vrVNY8IgVf10+/7z16cvP9R45fLI5FjWh1Ic0qKLuvvl/ffl/Q85fd1WtzW/rtkHp7g4ZF7BxoKZZmI4zI+mP+diyCtNWJ+VKiu7iEjzt9L+Ix1fN+XZzWfDppOdndbZyUz+D3sEcKfu2bWBAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/ba8f80e4182d2ecfd17af885a971adb8/a1b3b/diagram-vs-helm.webp 215w,
/static/ba8f80e4182d2ecfd17af885a971adb8/fc700/diagram-vs-helm.webp 430w,
/static/ba8f80e4182d2ecfd17af885a971adb8/521f7/diagram-vs-helm.webp 860w,
/static/ba8f80e4182d2ecfd17af885a971adb8/cbd37/diagram-vs-helm.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/ba8f80e4182d2ecfd17af885a971adb8/f7886/diagram-vs-helm.png 215w,
/static/ba8f80e4182d2ecfd17af885a971adb8/bc750/diagram-vs-helm.png 430w,
/static/ba8f80e4182d2ecfd17af885a971adb8/3e3fe/diagram-vs-helm.png 860w,
/static/ba8f80e4182d2ecfd17af885a971adb8/64756/diagram-vs-helm.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/ba8f80e4182d2ecfd17af885a971adb8/3e3fe/diagram-vs-helm.png&apos; alt=&apos;Helm vs Kustomize 비교 — 템플릿 변수 치환 vs 패치 오버레이 방식의 차이&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Helm vs Kustomize 비교 — 템플릿 변수 치환 vs 패치 오버레이 방식의 차이&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Helm&lt;/th&gt;
&lt;th&gt;Kustomize&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;학습 곡선&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go 템플릿 문법 필요&lt;/td&gt;
&lt;td&gt;YAML만 알면 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;설치&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;별도 바이너리&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;kubectl 내장&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;환경 분리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;values 파일&lt;/td&gt;
&lt;td&gt;overlay 디렉토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;버전 관리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;helm history&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;rollback&lt;/code&gt; 내장&lt;/td&gt;
&lt;td&gt;없음 (Git·ArgoCD에 의존)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;패키지 공유&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;레포지토리 생태계 풍부&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;제한적 (Git 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;복잡한 로직&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;템플릿 함수로 표현 가능&lt;/td&gt;
&lt;td&gt;제한적 (변환기·patch 조합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;검증·자동완성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;렌더링 후에만 가능&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;원본 YAML이 그대로 valid&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;외부 Chart 사용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;네이티브&lt;/td&gt;
&lt;td&gt;helmCharts로 래핑 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CD 통합&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ArgoCD·Flux 네이티브 지원&lt;/td&gt;
&lt;td&gt;ArgoCD·Flux 네이티브 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;선택 기준&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Helm이 유리한 경우:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부 Chart(ingress-nginx·cert-manager·prometheus 등) &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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Kustomize가 유리한 경우:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사내 마이크로서비스&lt;/strong&gt; YAML 관리 (학습 비용 최소)&lt;/li&gt;
&lt;li&gt;외부 Chart에 &lt;strong&gt;파일 몇 개만 덧붙이는&lt;/strong&gt; 상황&lt;/li&gt;
&lt;li&gt;엄격한 YAML 검증·리뷰 문화&lt;/li&gt;
&lt;li&gt;팀에 Go 템플릿 경험이 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;둘 다 쓰는 게 현실&lt;/strong&gt;: ingress-nginx는 Helm으로 설치하고, 우리 앱은 kustomize로 관리하는 식. ArgoCD는 &lt;strong&gt;둘 다 네이티브 지원&lt;/strong&gt;하므로 CD 레벨에서 통합됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;조합 패턴 — Helm 위에 Kustomize&lt;/h2&gt;
&lt;p&gt;Helm Chart를 base로 두고 kustomize overlay로 &lt;strong&gt;사내 규약&lt;/strong&gt;을 얹는 패턴.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# overlays/prod/kustomization.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kustomize.config.k8s.io/v1beta1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Kustomization

&lt;span class=&quot;token key atrule&quot;&gt;helmCharts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//charts.bitnami.com/bitnami
    &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15.4.4
    &lt;span class=&quot;token key atrule&quot;&gt;releaseName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;nginx
    &lt;span class=&quot;token key atrule&quot;&gt;valuesFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod.yaml

&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; common&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;labels&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;patch.yaml &lt;span class=&quot;token comment&quot;&gt;# 사내 라벨 규약 강제&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;실행 시 &lt;code class=&quot;language-text&quot;&gt;--enable-helm&lt;/code&gt; 플래그 필요&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kustomize build --enable-helm overlays/prod&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 패턴의 가치: &lt;strong&gt;외부 Chart의 기본 구조를 존중하면서, 사내 보안·관측·네이밍 규약을 일관되게 덧붙일 수 있습니다.&lt;/strong&gt; 매우 실용적입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ArgoCD 연동 — 5편 이어서&lt;/h2&gt;
&lt;p&gt;ArgoCD는 kustomization.yaml을 &lt;strong&gt;자동 감지&lt;/strong&gt;합니다. Application에 path만 지정하면 끝.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Application 매니페스트&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argoproj.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Application
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/javapark/k8s&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manifests.git
    &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app/overlays/prod
    &lt;span class=&quot;token key atrule&quot;&gt;kustomize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;namePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# ArgoCD 수준에서 추가 오버라이드 가능&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; nginx=nginx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;1.27.1
  &lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//kubernetes.default.svc
    &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CI 파이프라인에서 이미지 태그만 &lt;code class=&quot;language-text&quot;&gt;kustomize edit set image&lt;/code&gt;로 갱신 → Git push → ArgoCD 자동 sync. &lt;strong&gt;&lt;a href=&quot;/argocd-gitops-guide/&quot;&gt;ArgoCD 편&lt;/a&gt;&lt;/strong&gt;의 Self-Heal까지 붙이면 재현 가능한 배포 파이프라인 완성.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# CI에서 쓰는 패턴&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; apps/my-app/overlays/prod
kustomize edit &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; image &lt;span class=&quot;token assign-left variable&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx:v1.2.4
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-am&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chore: bump nginx to v1.2.4&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 3가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;patch가 안 먹힌다&quot;&lt;/h3&gt;
&lt;p&gt;대부분 &lt;code class=&quot;language-text&quot;&gt;target&lt;/code&gt; 불일치입니다. &lt;code class=&quot;language-text&quot;&gt;kind&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;name&lt;/code&gt;·(필요시) &lt;code class=&quot;language-text&quot;&gt;namespace&lt;/code&gt;가 정확히 일치하는지 확인. &lt;code class=&quot;language-text&quot;&gt;labelSelector&lt;/code&gt;로 여러 리소스를 타겟할 수도 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; add&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;label.yaml
    &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
      &lt;span class=&quot;token key atrule&quot;&gt;labelSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tier=backend&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) &quot;commonLabels가 Selector까지 바꿔서 배포가 깨진다&quot;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;commonLabels&lt;/code&gt;는 Selector에도 적용됩니다. 이미 배포된 Deployment의 Selector는 &lt;strong&gt;불변&lt;/strong&gt; — 새 라벨 추가 시 롤아웃이 실패합니다. 팀 라벨 같은 고정값은 처음부터 넣거나, &lt;code class=&quot;language-text&quot;&gt;labels&lt;/code&gt; 변환기(v4.5+)로 &lt;strong&gt;Selector 제외&lt;/strong&gt; 가능:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
    &lt;span class=&quot;token key atrule&quot;&gt;includeSelectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Selector는 건드리지 않음&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3) &quot;configMapGenerator 해시 때문에 Deployment가 매번 재배포된다&quot;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;원래 의도된 동작&lt;/strong&gt;입니다 (ConfigMap 내용 변경 시 Pod 새로고침). ConfigMap 해시를 원하지 않으면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;generatorOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;disableNameSuffixHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;단, 이러면 ConfigMap 변경이 Pod에 자동 반영되지 않으니 &lt;strong&gt;rollout restart를 별도로 해야&lt;/strong&gt; 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Helm을 이미 쓰고 있는데 kustomize로 갈아탈 가치가 있나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;작은 팀·단순 앱이면 Yes.&lt;/strong&gt; 템플릿 디버깅 시간이 많이 줄어듭니다. 단, 외부 Chart를 많이 쓰고 있다면 &lt;strong&gt;Helm을 유지하면서 일부만 kustomize로 관리&lt;/strong&gt;하는 하이브리드가 현실적입니다.&lt;/p&gt;
&lt;h3&gt;Q. &lt;code class=&quot;language-text&quot;&gt;kubectl apply -k&lt;/code&gt; 랑 &lt;code class=&quot;language-text&quot;&gt;kustomize build | kubectl apply -f -&lt;/code&gt; 차이는?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;내장 버전 vs 최신 버전의 차이&lt;/strong&gt;입니다. &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 내장 kustomize는 보통 standalone CLI보다 몇 버전 뒤처집니다. 신기능(Component, helmCharts 등)이 필요하면 standalone &lt;code class=&quot;language-text&quot;&gt;kustomize build&lt;/code&gt;를 쓰는 게 안전.&lt;/p&gt;
&lt;h3&gt;Q. 환경 수가 10개 넘어가면 overlay가 폭발하지 않나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Component&lt;/strong&gt;로 공통 기능을 뽑아내고, &lt;strong&gt;overlay는 최소한의 차이만&lt;/strong&gt; 담으세요. 같은 환경 그룹(예: dev 클러스터 3개)은 공통 overlay + 작은 patch로 처리. 정말 환경이 수십 개면 &lt;strong&gt;ArgoCD ApplicationSet&lt;/strong&gt;으로 자동 생성하는 게 맞습니다.&lt;/p&gt;
&lt;h3&gt;Q. Helm Chart를 kustomize base로 쓰려면?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;helmCharts&lt;/code&gt; 필드를 쓰고 &lt;code class=&quot;language-text&quot;&gt;kustomize build --enable-helm&lt;/code&gt;으로 빌드. ArgoCD는 Application의 &lt;code class=&quot;language-text&quot;&gt;kustomize.helmCharts&lt;/code&gt;로 직접 지원합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;kustomize의 진짜 가치는 &lt;strong&gt;&quot;YAML을 YAML답게 다루는 경험&quot;&lt;/strong&gt;입니다. 파일 하나하나가 그 자체로 valid하고, 에디터·lint·리뷰 도구가 모두 그대로 동작합니다. Helm이 주는 &quot;패키지성&quot;과는 다른 축의 가치라 &lt;strong&gt;둘 다 쓰는 게 정답&lt;/strong&gt;이라는 게 오늘의 결론.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kustomize = &lt;strong&gt;Base + Overlay&lt;/strong&gt;, 템플릿 없이 patch로 조합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 내장&lt;/strong&gt; — 설치 필요 없음&lt;/li&gt;
&lt;li&gt;가장 많이 쓰는 건 &lt;code class=&quot;language-text&quot;&gt;namespace&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;namePrefix&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;images&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;replicas&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;configMapGenerator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Patch는 Strategic Merge 기본 / 정교한 경우 JSON 6902&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helm과 대립이 아니라 조합&lt;/strong&gt; — 외부 Chart는 Helm, 사내 앱은 Kustomize&lt;/li&gt;
&lt;li&gt;ArgoCD가 둘 다 네이티브 지원하므로 &lt;strong&gt;CD 레벨에서 자유롭게 섞어 쓰기&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prometheus + Grafana 모니터링 스택&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 Helm과 Kustomize 중 주력으로 쓰는 건 어느 쪽인가요? 또는 이 둘을 어떻게 조합해 쓰고 계신지 댓글로 공유해주세요. 다음 편에 실전 패턴으로 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[ArgoCD 완벽 가이드 — Git을 진실의 원천으로 만드는 쿠버네티스 GitOps 파이프라인]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "누가 언제 뭘 배포했는지 기억이 안 납니다.  명령을 터미널 히스토리에서 뒤져야 합니다." 솔직히 저도 그랬습니다. 소규모 프로젝트 하나 돌리는데도 수동 가 쌓이면, 클러스터 상태와 Git 레포의 YAML…]]></description><link>https://blog.javapark.kr/argocd-gitops-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/argocd-gitops-guide/</guid><pubDate>Tue, 21 Apr 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;누가 언제 뭘 배포했는지 기억이 안 납니다. &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt; 명령을 터미널 히스토리에서 뒤져야 합니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. 소규모 프로젝트 하나 돌리는데도 수동 &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;가 쌓이면, 클러스터 상태와 Git 레포의 YAML이 &lt;strong&gt;조용히 어긋납니다&lt;/strong&gt;. &quot;운영에 긴급으로 꽂은 핫픽스&quot;가 Git에는 없고, 한 달 뒤 누가 그 YAML을 다시 &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;하면 그 핫픽스가 증발합니다. 재현 불가능한 상태라는 게 이런 식으로 쌓입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;ArgoCD는 &quot;Git 레포를 진실의 원천으로 두고, 클러스터가 알아서 그 상태에 수렴하게 만드는&quot; GitOps 컨트롤러입니다.&lt;/strong&gt; 배포를 더 이상 &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt;로 하지 않고, &lt;strong&gt;Git에 push만 하면&lt;/strong&gt; ArgoCD가 감지해서 반영합니다. 핫픽스도 Git에 남고, 드리프트(drift)가 생기면 경고가 뜨거나 자동 복원됩니다. 오늘은 이 구조부터 실전 셋업·Sync 정책·Helm/Kustomize 조합·운영 팁까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt; · &lt;a href=&quot;/kubectl-essential-commands/&quot;&gt;kubectl 실전 명령어&lt;/a&gt; · &lt;a href=&quot;/k9s-essential-guide/&quot;&gt;k9s 완벽 가이드&lt;/a&gt; · &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 완벽 가이드&lt;/a&gt;부터 읽으시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GitOps란? — 한 줄 정의와 기존 방식과의 차이&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;GitOps는 &quot;시스템의 원하는 상태를 Git에 선언하고, 자동화 도구가 그 상태에 실제 시스템을 수렴시키는&quot; 운영 방식&lt;/strong&gt;입니다. 2017년 Weaveworks가 만든 용어지만 지금은 사실상 K8S 표준.&lt;/p&gt;
&lt;p&gt;기존 CI/CD(Push 모델)와 GitOps(Pull 모델)의 차이:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;전통적 CD (Push)&lt;/th&gt;
&lt;th&gt;GitOps (Pull)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;배포 트리거&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI에서 &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt; 푸시&lt;/td&gt;
&lt;td&gt;Git 변경을 &lt;strong&gt;클러스터 내 컨트롤러&lt;/strong&gt;가 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자격 증명&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI가 클러스터 권한 보유 → 외부 노출&lt;/td&gt;
&lt;td&gt;컨트롤러가 클러스터 내부 → 외부 노출 X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;드리프트 감지&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;거의 불가능&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;자동&lt;/strong&gt; (원하는 상태 vs 실제 상태 비교)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;롤백&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이전 이미지 재배포 수동 트리거&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Git revert&lt;/strong&gt; 한 번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;감사 로그&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI 로그·명령 히스토리 조합&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Git 커밋 히스토리&lt;/strong&gt;가 그대로 감사 로그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;멀티 클러스터&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;환경별 CI 잡 중복&lt;/td&gt;
&lt;td&gt;환경별 디렉토리·브랜치로 선언&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심은 &lt;strong&gt;&quot;클러스터가 Git을 구독한다&quot;&lt;/strong&gt;는 역전입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/f5b3ae1610d0a40ab9415417d2106f2c/64756/diagram-gitops-flow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJ0lEQVR42iWS246bMBRF+YJOCCTcbAwxFxOuNmDA3MJ0JqP2sVL//1d6MpWWtvzgpXNsbe3k1z/ShTRHJj+i+mHn6nxfnGwt+mcxfKFkdKlEyQRgpnw2Q8LZi0c3HjWQrWSKqzVrj7s4gnK2i5XkG++ffHh6hXIK5WUKFQsqF0gvn91idu6TE0lNx+XO16/hZ1stIRVBKGjYRrRL0ikIhLu++3//4M9fSB6A2+2ABwexWlGnmbgK477k73EyINJ4hCMicCD8sKWJdOvlNj7Z9ps0+5UNVjZCXlNp5+P11r5kEMJkcnxueZWNagfAtetzmg5wiVRbUG7XqDepMG+tEUIKJxsuoQC5xmHLii1myiPCwY3nAxz54hZJp5qj/oMNT5sNZtSatDWoMGh7YaMZcO3qN5jwmM1xtsC2LnrNfMmE06R//VaugnqzEgkzv+FGCLI6k0azCMdhl+QbyLCzjRuQvxEh7e1yCuo9rHYnkTqpwTwHIIvLfdR9kANRMrn1H0ocXT7XbCyToWRjlY4slna7ovcnfnyS/dNVhykWg89nsZz5rPtcc+DBUV+yqWBTlakym3Om8nS6pyOlnVVO13a5CGWJ2RLLpVZGM+v1fCqmEwE5kl42uNmA8hHnE3ATe9QfSXvE4oGaJeiOQGxOqfS0O7NeZ/LE5Fvav2GuuVDAu4LSoGJF5fpqUrXhev3fJzebvGxy2WjF0qCdSbsz7U+0M6JB98U/zjt1q+x1yZIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/f5b3ae1610d0a40ab9415417d2106f2c/a1b3b/diagram-gitops-flow.webp 215w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/fc700/diagram-gitops-flow.webp 430w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/521f7/diagram-gitops-flow.webp 860w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/cbd37/diagram-gitops-flow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/f5b3ae1610d0a40ab9415417d2106f2c/f7886/diagram-gitops-flow.png 215w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/bc750/diagram-gitops-flow.png 430w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/3e3fe/diagram-gitops-flow.png 860w,
/static/f5b3ae1610d0a40ab9415417d2106f2c/64756/diagram-gitops-flow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/f5b3ae1610d0a40ab9415417d2106f2c/3e3fe/diagram-gitops-flow.png&apos; alt=&apos;전통 CD(Push) vs GitOps(Pull) 배포 흐름 비교 — CI 주도 vs 컨트롤러 주도 구조&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;전통 CD(Push) vs GitOps(Pull) 배포 흐름 비교 — CI 주도 vs 컨트롤러 주도 구조&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ArgoCD란? — 한 줄 정의&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/&quot;&gt;ArgoCD&lt;/a&gt;는 &lt;strong&gt;쿠버네티스 전용 GitOps 컨트롤러&lt;/strong&gt;입니다. CNCF Graduated 프로젝트(졸업 단계)로, 사실상 K8S GitOps 표준입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;선언&lt;/strong&gt;: Git 레포에 K8S manifest(YAML·Helm·Kustomize) 저장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;감지&lt;/strong&gt;: ArgoCD가 주기적으로 레포를 풀(pull)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비교&lt;/strong&gt;: 원하는 상태(Git) vs 실제 상태(클러스터) 차이 계산&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수렴&lt;/strong&gt;: 자동 또는 수동으로 Apply&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CLI(&lt;code class=&quot;language-text&quot;&gt;argocd&lt;/code&gt;), 웹 UI, Kubernetes CRD(&lt;code class=&quot;language-text&quot;&gt;Application&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;AppProject&lt;/code&gt;) 세 가지 인터페이스를 모두 제공합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ArgoCD 아키텍처 — 4개 컴포넌트&lt;/h2&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;&lt;strong&gt;API Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;웹 UI·CLI 요청을 받는 gRPC/REST 서버, 인증·RBAC 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Git 레포를 클론·캐시, manifest 렌더링 (Helm·Kustomize 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application Controller&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실제 동기화 엔진 — 비교·Apply·상태 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;레퍼런스 데이터 캐시 (성능용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 4개가 하나의 K8S 네임스페이스(&lt;code class=&quot;language-text&quot;&gt;argocd&lt;/code&gt;)에 Deployment로 돌고, 외부에서는 Ingress로 UI만 노출하는 게 일반적입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;핵심 개념 4가지 — Application, Project, Sync, Health&lt;/h2&gt;
&lt;h3&gt;1) Application&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;무엇을 어디에 배포할지&quot;&lt;/strong&gt; 선언한 CRD. ArgoCD에서 다루는 가장 작은 단위.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argoproj.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Application
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argocd
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/javapark/k8s&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manifests.git
    &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app/overlays/prod &lt;span class=&quot;token comment&quot;&gt;# Kustomize 오버레이&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//kubernetes.default.svc &lt;span class=&quot;token comment&quot;&gt;# 같은 클러스터&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
  &lt;span class=&quot;token key atrule&quot;&gt;syncPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;automated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;prune&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;selfHeal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) AppProject&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;RBAC·정책 경계.&lt;/strong&gt; 여러 Application을 묶어서 &quot;이 프로젝트는 이 Git 레포와 이 네임스페이스만 건드릴 수 있다&quot;를 제한.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; argoproj.io/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AppProject
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; platform
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;sourceRepos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/javapark/k8s&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manifests.git
  &lt;span class=&quot;token key atrule&quot;&gt;destinations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//kubernetes.default.svc
      &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//kubernetes.default.svc
      &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;staging&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;clusterResourceWhitelist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Namespace&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;팀·환경 단위로 권한 분리할 때 필수.&lt;/p&gt;
&lt;h3&gt;3) Sync&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Git의 원하는 상태를 클러스터에 실제로 적용&lt;/strong&gt;하는 동작. 수동 또는 자동.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manual Sync&lt;/strong&gt;: 사용자가 UI/CLI에서 &lt;code class=&quot;language-text&quot;&gt;Sync&lt;/code&gt; 클릭/명령&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto Sync&lt;/strong&gt;: Git 변경 감지 시 자동 적용
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;prune: true&lt;/code&gt; — Git에서 지워진 리소스는 클러스터에서도 자동 삭제&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;selfHeal: true&lt;/code&gt; — 클러스터에서 수동 수정된 내용을 Git 기준으로 &lt;strong&gt;자동 되돌림&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4) Health / Sync Status&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;두 가지 상태를 별개로 추적&lt;/strong&gt;합니다.&lt;/p&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;&lt;strong&gt;Sync Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Synced&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클러스터 = Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;OutOfSync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;차이 있음 (아직 Sync 전)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Unknown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;비교 불가 (초기 또는 오류)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Health Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Healthy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 리소스 정상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Progressing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;배포 진행 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Degraded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;하나 이상 리소스 비정상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Missing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;리소스 누락&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;Synced + Degraded&lt;/code&gt; 조합이 가장 위험합니다. &quot;Git대로 적용했는데 앱이 죽어있다&quot; → 이미지 태그 오류·리소스 부족 등 &lt;strong&gt;앱 자체 문제&lt;/strong&gt;. 알림은 이 조합을 최우선으로 걸어두세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/1e3c35cc1b6d072c7a3e22ae18071b8e/64756/diagram-sync-states.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMklEQVR42h2OB46jQAAE+cFpjW0MOJDj5GECMyQbs9q9/P/XnPekUqvVUkntfPPhLh931XStbYrnlMwpXmqxkf57RBYPTH6zBLkOSxOUNiiHsDJh2ft5H+TKeQugmxk3sxGaUvXM9LMcP5v1F5h/3vTqdWtM56IVOdCIadapEqoCiLwRYSad3RntcrbLuQvUab67anSZdbthT8yR2hMdayonKwbbjYaLjmPKEWEA0TDrHPdGXGpdPnjdWP/4HS0fsd2y4T3q1xMdXjKSw8f2eKzz8/nox7XTE5NDDZifMGd/I0cxnfr7Hqoj0D4ZfDYG3eTzKWRTyOeCaIgVwApSDakEqGshqxrix9TZx8wlds+HAzYnNh7p67D1Xj6fgm72+JwRhfmX1iJeA1q3tGpxWeNTRJ1TxLNK5o1OK9k2GrUag75p+7RWEJoOD6CSChkGdVXQMidFRoqUZAn2rsQJYi5yKQvFCrnx58Yfn2J7im0S7xNbVWNEbf4sfz+mnwxOL2g71qnIb9S74C+5B8NMFpgLWCpc9bg2tB06MNLGokKCQsC6h5UGr57LJhNlzLMbPYTIOaeSFaorNSkkq/r/GFpolAmYdiBhdcxgoVDavZY2YtWVlFeSnfEhBE6QqQSYDA0JtAk0JZmA2Vq9NvzesIWwWfIF0rFR9xz2l5SdUxYk1IvJLoBOUGi/NT6wZziGcHzlhUxnNIZgCFp7ae2tMedKhbX2c3FI2D6mbxFxY/4Won/kfIF8a7kX5AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/1e3c35cc1b6d072c7a3e22ae18071b8e/a1b3b/diagram-sync-states.webp 215w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/fc700/diagram-sync-states.webp 430w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/521f7/diagram-sync-states.webp 860w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/cbd37/diagram-sync-states.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/1e3c35cc1b6d072c7a3e22ae18071b8e/f7886/diagram-sync-states.png 215w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/bc750/diagram-sync-states.png 430w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/3e3fe/diagram-sync-states.png 860w,
/static/1e3c35cc1b6d072c7a3e22ae18071b8e/64756/diagram-sync-states.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/1e3c35cc1b6d072c7a3e22ae18071b8e/3e3fe/diagram-sync-states.png&apos; alt=&apos;Sync × Health 상태 매트릭스 — 4가지 조합별 시나리오와 대응 액션&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Sync × Health 상태 매트릭스 — 4가지 조합별 시나리오와 대응 액션&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치 — Helm 한 줄&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 네임스페이스 + Helm 설치&lt;/span&gt;
kubectl create namespace argocd
helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; argo https://argoproj.github.io/argo-helm
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; argocd argo/argo-cd &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; argocd

&lt;span class=&quot;token comment&quot;&gt;# 초기 admin 비밀번호 확인&lt;/span&gt;
kubectl &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; argocd get secret argocd-initial-admin-secret &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{.data.password}&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; base64 &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# UI 접근 (port-forward로 임시 확인)&lt;/span&gt;
kubectl port-forward svc/argocd-server &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; argocd &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;:443
&lt;span class=&quot;token comment&quot;&gt;# → https://localhost:8080 접속, 계정: admin&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CLI도 같이 설치:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# macOS&lt;/span&gt;
brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; argocd

&lt;span class=&quot;token comment&quot;&gt;# Linux (바이너리)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-sSL&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x /usr/local/bin/argocd

&lt;span class=&quot;token comment&quot;&gt;# 로그인&lt;/span&gt;
argocd login localhost:8080&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;운영에서는 Helm &lt;code class=&quot;language-text&quot;&gt;values.yaml&lt;/code&gt;로 Ingress·SSO·노드셀렉터 등을 제대로 설정하세요. 기본 설치는 &lt;strong&gt;데모용&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 — 첫 Application 배포까지&lt;/h2&gt;
&lt;h3&gt;1단계: Git 레포 구조 준비&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;k8s-manifests/
└── apps/
    └── my-app/
        ├── base/
        │   ├── deployment.yaml
        │   ├── service.yaml
        │   └── kustomization.yaml
        └── overlays/
            ├── staging/
            │   ├── patch.yaml
            │   └── kustomization.yaml
            └── prod/
                ├── patch.yaml
                └── kustomization.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: Application 생성&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;argocd app create my-app-prod &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--repo&lt;/span&gt; https://github.com/javapark/k8s-manifests.git &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--path&lt;/span&gt; apps/my-app/overlays/prod &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --dest-server https://kubernetes.default.svc &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --dest-namespace production &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --sync-policy automated &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --auto-prune &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --self-heal&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또는 YAML(&lt;code class=&quot;language-text&quot;&gt;Application&lt;/code&gt; CRD)로 선언하고 &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt; — &lt;strong&gt;Application 자체도 GitOps로 관리&lt;/strong&gt;하는 게 정석입니다 (이를 &quot;App of Apps&quot; 패턴이라 합니다).&lt;/p&gt;
&lt;h3&gt;3단계: 배포 확인&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;argocd app get my-app-prod
argocd app &lt;span class=&quot;token function&quot;&gt;sync&lt;/span&gt; my-app-prod       &lt;span class=&quot;token comment&quot;&gt;# 수동 sync (필요시)&lt;/span&gt;
argocd app logs my-app-prod       &lt;span class=&quot;token comment&quot;&gt;# 해당 앱 Pod 로그&lt;/span&gt;
argocd app rollback my-app-prod &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 3번째 리비전으로 롤백&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;UI(&lt;code class=&quot;language-text&quot;&gt;https://localhost:8080&lt;/code&gt;)에서는 &lt;strong&gt;트리 형태로 Deployment → ReplicaSet → Pod&lt;/strong&gt;까지 시각화됩니다. &lt;code class=&quot;language-text&quot;&gt;k9s&lt;/code&gt;와는 다른 축의 시각화라 둘 다 쓰는 게 편합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Helm / Kustomize 조합 — 실무의 80%&lt;/h2&gt;
&lt;p&gt;ArgoCD는 Git 레포 안에서 &lt;strong&gt;자동으로 Helm Chart·Kustomize를 감지&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;h3&gt;Helm Chart를 ArgoCD로 배포&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/javapark/k8s&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manifests.git
    &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; charts/my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
    &lt;span class=&quot;token key atrule&quot;&gt;helm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;valueFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; values.yaml
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod.yaml
      &lt;span class=&quot;token key atrule&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; image.tag
          &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1.2.4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;외부 Helm 리포도 가능:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//charts.bitnami.com/bitnami
    &lt;span class=&quot;token key atrule&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15.4.4
    &lt;span class=&quot;token key atrule&quot;&gt;helm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;valueFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $values/charts/nginx/values&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prod.yaml
  &lt;span class=&quot;token key atrule&quot;&gt;sources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 멀티 소스 — chart는 bitnami, values는 내 레포&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//charts.bitnami.com/bitnami
      &lt;span class=&quot;token key atrule&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
      &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15.4.4
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/javapark/k8s&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;manifests.git
      &lt;span class=&quot;token key atrule&quot;&gt;targetRevision&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main
      &lt;span class=&quot;token key atrule&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; values&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Kustomize&lt;/h3&gt;
&lt;p&gt;overlay 경로만 지정하면 끝:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app/overlays/prod
  &lt;span class=&quot;token comment&quot;&gt;# ArgoCD가 kustomization.yaml을 자동 인식&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Helm vs Kustomize 선택 기준은 이전 &lt;a href=&quot;/helm-package-management/&quot;&gt;Helm 편&lt;/a&gt;에서 다뤘습니다.&lt;/strong&gt; ArgoCD는 둘 다 native 지원하므로 팀 취향대로 가시면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Sync 정책 상세 — Auto-Sync, Prune, Self-Heal&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;automated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git 변경 감지 시 자동 Sync&lt;/td&gt;
&lt;td&gt;dev/staging: ✓ / prod: 초기엔 manual, 익숙해지면 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git에서 삭제된 리소스를 클러스터에서도 삭제&lt;/td&gt;
&lt;td&gt;실수 방지 위해 &lt;strong&gt;PR 병합 단계에서만&lt;/strong&gt; 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;selfHeal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;누군가 &lt;code class=&quot;language-text&quot;&gt;kubectl edit&lt;/code&gt;로 수동 변경한 걸 Git 상태로 자동 복원&lt;/td&gt;
&lt;td&gt;prod 필수. &lt;strong&gt;&quot;Git이 진실&quot;을 강제&lt;/strong&gt;하는 핵심 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;selfHeal: true&lt;/code&gt;가 GitOps의 꽃입니다. 이게 있으면 &lt;strong&gt;&quot;운영에서 kubectl edit으로 대충 고쳐놓고 Git에 반영 안 함&quot;&lt;/strong&gt; 패턴이 원천 차단됩니다.&lt;/p&gt;
&lt;h3&gt;Sync Waves — 순서가 중요한 경우&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;argocd.argoproj.io/sync-wave&lt;/code&gt; 애노테이션으로 리소스별 적용 순서를 지정:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;argocd.argoproj.io/sync-wave&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-1&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 먼저 적용&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예: &lt;strong&gt;Namespace(wave -2) → ConfigMap(wave -1) → Deployment(wave 0)&lt;/strong&gt; 순으로 보장.&lt;/p&gt;
&lt;h3&gt;Sync Hooks — DB 마이그레이션 등&lt;/h3&gt;
&lt;p&gt;Pre-Sync, Sync, Post-Sync, Sync-Fail 훅으로 Job을 끼워넣을 수 있습니다. DB 스키마 마이그레이션에 유용:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;argocd.argoproj.io/hook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; PreSync
    &lt;span class=&quot;token key atrule&quot;&gt;argocd.argoproj.io/hook-delete-policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; HookSucceeded&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;App of Apps — ArgoCD 자기 자신 관리하기&lt;/h2&gt;
&lt;p&gt;하나의 &quot;부모&quot; Application이 여러 자식 Application을 Git에서 관리하는 패턴.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;k8s-manifests/
└── apps/
    ├── _bootstrap/
    │   └── applications.yaml   # 부모 App — apps/children/*.yaml 을 감시
    └── children/
        ├── nginx.yaml          # Application (nginx)
        ├── redis.yaml          # Application (redis)
        └── monitoring.yaml     # Application (prometheus + grafana)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;새 서비스 추가 = YAML 한 개 PR&lt;/strong&gt;. ArgoCD가 알아서 감지·배포&lt;/li&gt;
&lt;li&gt;ArgoCD 자체 설정도 Git으로 관리 (재설치·클러스터 이관 시 스크립트 재실행 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;운영 클러스터 1개당 부모 App 1개 두는 게 표준 패턴입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;멀티 클러스터 운영&lt;/h2&gt;
&lt;p&gt;ArgoCD 한 인스턴스가 여러 클러스터를 관리할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 클러스터 등록&lt;/span&gt;
argocd cluster &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; staging-cluster-context
argocd cluster &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; prod-cluster-context

&lt;span class=&quot;token comment&quot;&gt;# 확인&lt;/span&gt;
argocd cluster list&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Application의 &lt;code class=&quot;language-text&quot;&gt;destination.server&lt;/code&gt;만 클러스터 URL로 지정하면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//staging&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cluster.example.com
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;관리 클러스터는 ArgoCD 없음, 중앙 ArgoCD가 푸시&lt;/strong&gt;하는 구조 — 이걸 &lt;strong&gt;Hub-and-Spoke&lt;/strong&gt;라고 합니다. 소규모에선 단일 클러스터 1:1이 충분하지만, 3개 이상 되면 Hub 구조가 관리 비용을 크게 낮춥니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;보안·운영 팁 5가지&lt;/h2&gt;
&lt;h3&gt;1) UI·API는 SSO 뒤에&lt;/h3&gt;
&lt;p&gt;기본 &lt;code class=&quot;language-text&quot;&gt;admin&lt;/code&gt; 계정은 로컬 전용. 운영에선 &lt;strong&gt;Google/GitHub/SAML OIDC&lt;/strong&gt; 연동. &lt;code class=&quot;language-text&quot;&gt;argocd-cm&lt;/code&gt; ConfigMap에 설정:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;dex.config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    connectors:
      - type: oidc
        id: google
        name: Google
        config:
          issuer: https://accounts.google.com
          clientID: ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) RBAC는 AppProject로&lt;/h3&gt;
&lt;p&gt;팀별 Project 분리 + &lt;code class=&quot;language-text&quot;&gt;argocd-rbac-cm&lt;/code&gt;으로 역할 정의. &quot;dev 팀은 dev namespace만&quot; 같은 강제.&lt;/p&gt;
&lt;h3&gt;3) Notifications — Slack / Telegram 알림&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://argocd-notifications.readthedocs.io/&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;argocd-notifications&lt;/code&gt;&lt;/a&gt; 컨트롤러로 Sync 실패·Degraded 시 알림.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 예: Telegram&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;service.telegram&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
  token: $telegram-token
  chatIDs: [&quot;-100123...&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4) Image Updater — 이미지 자동 갱신&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://argocd-image-updater.readthedocs.io/&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;argocd-image-updater&lt;/code&gt;&lt;/a&gt; 애드온. 컨테이너 레지스트리를 폴링해 &lt;strong&gt;새 이미지 태그를 Git에 자동 PR·커밋&lt;/strong&gt;. Git push → ArgoCD sync가 연쇄로 일어나 완전 자동화 가능.&lt;/p&gt;
&lt;h3&gt;5) 진실의 원천은 Git 하나만&lt;/h3&gt;
&lt;p&gt;&quot;긴급하니 &lt;code class=&quot;language-text&quot;&gt;kubectl edit&lt;/code&gt;&quot; 유혹이 오는 순간이 반드시 옵니다. 그때마다 &lt;strong&gt;selfHeal이 되돌릴 것&lt;/strong&gt;을 기억하세요. 진짜 긴급이면 Git에 핫픽스 커밋 후 sync — 1분이면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 3가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;Sync는 됐는데 Health가 Degraded&quot;&lt;/h3&gt;
&lt;p&gt;→ 앱 자체 문제. Pod logs·events 확인. ArgoCD는 &quot;Git대로 뿌렸을 뿐&quot; — 앱이 죽는 이유는 Git 상태 밖의 문제(이미지 손상·환경변수 오타·외부 의존성).&lt;/p&gt;
&lt;h3&gt;2) &quot;Sync가 무한 반복된다&quot;&lt;/h3&gt;
&lt;p&gt;→ 컨트롤러나 defaulting webhook이 자동으로 필드를 추가하는데 Git에 없어서 &lt;code class=&quot;language-text&quot;&gt;OutOfSync&lt;/code&gt;가 계속 뜸. &lt;code class=&quot;language-text&quot;&gt;ignoreDifferences&lt;/code&gt;로 특정 필드 제외:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;ignoreDifferences&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps
      &lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
      &lt;span class=&quot;token key atrule&quot;&gt;jsonPointers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; /spec/replicas &lt;span class=&quot;token comment&quot;&gt;# HPA가 관리하는 replicas 무시&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3) &quot;Prune으로 중요한 리소스가 지워졌다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;automated.prune: true&lt;/code&gt;의 위험. 학습 단계에선 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;syncPolicy.syncOptions: [Validate=true, Prune=false]&lt;/code&gt;&lt;/strong&gt; 로 끄고, 수동 sync 때만 &lt;code class=&quot;language-text&quot;&gt;--prune&lt;/code&gt; 명시. 익숙해지면 자동화.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Flux와 ArgoCD 중 뭘 써야 하나요?&lt;/h3&gt;
&lt;p&gt;둘 다 GitOps 표준, 기능 유사. 차이:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ArgoCD&lt;/strong&gt;: 강력한 UI, 멀티 테넌시 · 멀티 클러스터에 강함, 학습 곡선 완만&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flux&lt;/strong&gt;: UI 없음(별도 도구 연동), Helm 네이티브 통합, 경량, Kustomize 생태계에 밀착&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;입문은 ArgoCD 추천.&lt;/strong&gt; UI가 있어서 감 잡기 쉽고, 팀에 공유하기도 편합니다.&lt;/p&gt;
&lt;h3&gt;Q. CI(GitHub Actions)와 역할 분담이 궁금합니다.&lt;/h3&gt;
&lt;p&gt;명확히 나뉩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CI&lt;/strong&gt;: 빌드·테스트·이미지 푸시 → 성공 시 &lt;strong&gt;manifest 레포의 이미지 태그 업데이트 PR&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CD&lt;/strong&gt;: ArgoCD가 manifest 레포 변경을 감지 → sync&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CI가 더 이상 &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt;를 치지 않아서 &lt;strong&gt;클러스터 접근 권한을 CI에 줄 필요가 없어집니다&lt;/strong&gt;. 보안 관점에서 큰 개선.&lt;/p&gt;
&lt;h3&gt;Q. 선언형 대신 CLI로만 배포해도 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;argocd app create ...&lt;/code&gt;로 가능하지만 &lt;strong&gt;Application 자체를 Git에 YAML로 넣는 게 원칙&lt;/strong&gt;입니다. 그래야 ArgoCD 재설치·백업 복원·클러스터 이관이 자동화됩니다. 처음엔 CLI로 실험하고 운영은 Application YAML로.&lt;/p&gt;
&lt;h3&gt;Q. Application 수가 많아지면?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ApplicationSet&lt;/strong&gt;이라는 상위 CRD를 쓰세요. Git 디렉토리 패턴·클러스터 리스트·Pull Request 기반으로 Application을 &lt;strong&gt;자동 생성&lt;/strong&gt;합니다. 마이크로서비스 수십 개 있는 조직에서 필수.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;ArgoCD를 붙이면 K8S 운영의 질적 전환이 일어납니다. &quot;Git에 뭔가 있는 것&quot;과 &quot;실제 돌고 있는 것&quot;이 일치하는 상태가 기본값이 되고, 배포 실수의 대부분이 구조적으로 차단됩니다.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitOps는 &lt;strong&gt;&quot;Git을 진실의 원천, 컨트롤러가 클러스터를 수렴&quot;&lt;/strong&gt; 시키는 운영 방식&lt;/li&gt;
&lt;li&gt;ArgoCD 핵심 CRD: &lt;strong&gt;Application · AppProject · (ApplicationSet)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Sync Status와 Health Status는 &lt;strong&gt;별개&lt;/strong&gt; — Degraded + Synced 조합에 주목&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;selfHeal: true&lt;/code&gt;가 GitOps의 진짜 가치&lt;/li&gt;
&lt;li&gt;Helm·Kustomize는 ArgoCD에서 native 지원 — 둘 중 하나 고르면 됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application 자체도 Git으로 관리 (App of Apps)&lt;/strong&gt; — 운영 성숙도의 지표&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; kustomize로 환경별 YAML 관리 (Helm과 비교·조합)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prometheus + Grafana 모니터링 스택&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 ArgoCD를 도입하면서 가장 큰 장벽이 뭐였나요? &quot;운영에서 kubectl edit 습관 못 버리는 것&quot;이 1위일 거라 예상합니다. 댓글로 공유해주시면 다음 편에 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude Code Subagent 완벽 가이드 — 메인 세션 컨텍스트를 지키는 전문 에이전트 활용법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Claude Code…]]></description><link>https://blog.javapark.kr/subagent-essential-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/subagent-essential-guide/</guid><pubDate>Tue, 21 Apr 2026 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Claude Code로 큰 프로젝트 탐색하다가 컨텍스트가 순식간에 차버린 경험, 한 번씩 있으시죠?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. &lt;code class=&quot;language-text&quot;&gt;Grep&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;Read&lt;/code&gt;로 파일 수십 개 열다 보면 정작 본 작업 시작도 전에 토큰이 반쯤 날아가 있고, 한 세션 안에서 서로 다른 성격의 작업(탐색·계획·리뷰·보안점검)을 섞으면 프롬프트가 중구난방이 됩니다. 디버깅 한 번 하자고 메인 대화 히스토리가 로그로 뒤덮이는 것도 흔한 일이죠.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Subagent는 &quot;메인 세션과 컨텍스트를 공유하지 않는 독립 에이전트&quot;로, 이 문제를 구조적으로 해결합니다.&lt;/strong&gt; 무거운 탐색·리뷰·연구 작업을 서브 에이전트로 분리하면 결과만 메인으로 돌아오고, 중간 과정은 메인 컨텍스트를 오염시키지 않습니다. 여러 작업을 &lt;strong&gt;병렬로&lt;/strong&gt; 돌릴 수도 있습니다. 오늘은 이 개념부터 커스텀 에이전트 제작, 실전 설계 원칙까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이 포스팅은 &lt;strong&gt;Claude Code Agent 시리즈 1편&lt;/strong&gt;입니다. 다음 편 &lt;a href=&quot;#&quot;&gt;Agent Teams&lt;/a&gt;에서는 &quot;서브 에이전트끼리 서로 통신하는 한 단계 위 개념&quot;을 다룰 예정입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Subagent란? — 한 줄 정의&lt;/h2&gt;
&lt;p&gt;Subagent는 &lt;strong&gt;Claude Code가 메인 세션에서 Task(Agent) 도구로 호출하는 독립된 Claude 인스턴스&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;핵심 3가지 특성:&lt;/p&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;&lt;strong&gt;독립 컨텍스트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;메인 세션의 대화 기록을 보지 않음. 자체 컨텍스트 윈도우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;제한된 도구&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;에이전트별로 허용된 tool만 사용 (예: 읽기 전용, Bash 차단 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;결과만 반환&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중간 과정(tool 호출·사고)은 메인에 안 보임, 최종 결과만 돌아옴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;비유하자면 — &lt;strong&gt;사무실 본인(메인)은 큰 회의를 주재하고, 리서치 과제는 인턴(Subagent)에게 위임&lt;/strong&gt;합니다. 인턴은 혼자 책장에서 자료를 뒤지고 요약본만 들고 옵니다. 본인 회의실은 조용히 유지됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/a2bc7fa4c78cb0d7b883cbc4d7350868/64756/diagram-anatomy.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVR42i2S2W7bMBBF9QmJ7VjWLlLiqpXUQouyZSmyYwcpkIcW/f9PKd0WOJiHAc7cwWCsjZuDVEDSM7HwemL5yKpVHB/18CBiKfobk6uHlE+GgA4B089KBg8r07S2bo6ISGnX6LvolrwcS3kVx49KPdrxZzN+Z+0jwKeYTYAvgC0RnXykDW7aP+Ve39T5gbgyI0iuSN77iXBhL/pfZW/kH7j8TLIPxB+Yf0Z4tqPWBcqBRvYLXJzq7gOyIcYtIB2kfYg7Hw0mKsnWJLui/A74aoftPpD7oHnzpR11B9AZucRM1fIeoqMbCxdIH8qYqZidITPmLc1vpgK22lDt4/a/HHaHuLX2YQXoEeDFiYQDagcIL5GQDzA7h+w9oktILhG9ROTiZOMhVXbQmBVM8j5qLDuq00zn5WwCnVia5CBpwkL79QVPv5n+Juorrm8HOnnl3anudnHbpXoX92+hsA5AQqZQptJsMKB8xOUY8yHgZ9R+kv4raT+D8uqw+S3ROzhsY/UayNew2YW15UCZ8GNizFzjYiTVmdZnkOmAnbzi3ctmh88uX1y27Px24wnDqy+Mv/Ury4WNkdO/MipO//CJstFg83nPLjadbDLb+LKhegPVi9e8+NKw8UrLS7uUP9dG+YDzoe7mTq+tvgq1Vu2FNwuVK64WUq1YrCEdg1R5sHdAs3ELyzxa/LztmOQnA64mWk+kmkh5MuPMLQHVgAyQaIB0CJUPWjeSHmi2XvkHcSB61e9nqDoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/a2bc7fa4c78cb0d7b883cbc4d7350868/a1b3b/diagram-anatomy.webp 215w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/fc700/diagram-anatomy.webp 430w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/521f7/diagram-anatomy.webp 860w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/cbd37/diagram-anatomy.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/a2bc7fa4c78cb0d7b883cbc4d7350868/f7886/diagram-anatomy.png 215w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/bc750/diagram-anatomy.png 430w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/3e3fe/diagram-anatomy.png 860w,
/static/a2bc7fa4c78cb0d7b883cbc4d7350868/64756/diagram-anatomy.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/a2bc7fa4c78cb0d7b883cbc4d7350868/3e3fe/diagram-anatomy.png&apos; alt=&apos;Subagent 구조도 — 메인 세션과 격리된 독립 컨텍스트에서 실행되는 서브 에이전트들&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Subagent 구조도 — 메인 세션과 격리된 독립 컨텍스트에서 실행되는 서브 에이전트들&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 쓰는가 — 3가지 가치&lt;/h2&gt;
&lt;h3&gt;1) 컨텍스트 격리 — 가장 큰 이유&lt;/h3&gt;
&lt;p&gt;대량 탐색·로그 확인·문서 읽기 같은 &quot;보조 작업&quot;이 메인 대화의 토큰을 먹어치우는 걸 막습니다. 같은 프로젝트를 3일째 붙잡고 있는 세션일수록 체감 효과가 큽니다.&lt;/p&gt;
&lt;h3&gt;2) 병렬화&lt;/h3&gt;
&lt;p&gt;독립된 작업이라면 &lt;strong&gt;한 메시지 안에서 여러 Subagent를 동시에 호출&lt;/strong&gt; 가능합니다. 예: 프론트엔드 코드 스캔 + 백엔드 API 점검 + 테스트 커버리지 확인을 동시 진행.&lt;/p&gt;
&lt;h3&gt;3) 특화 (Specialization)&lt;/h3&gt;
&lt;p&gt;각 에이전트에 &lt;strong&gt;전용 시스템 프롬프트 + 도구 조합&lt;/strong&gt;을 세팅할 수 있습니다. 보안 리뷰어는 Read·Grep만 허용, 테스트 러너는 Bash까지 허용 — 식으로 &lt;strong&gt;최소 권한 + 전문성&lt;/strong&gt;을 동시에 얻습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;내장 Subagent 지도 — 기본 탑재 5가지&lt;/h2&gt;
&lt;p&gt;Claude Code에는 바로 쓸 수 있는 공식 에이전트들이 있습니다 (버전·설정에 따라 구성 다름).&lt;/p&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;&lt;strong&gt;general-purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;범용 복잡 작업 — 모든 도구 사용 가능&lt;/td&gt;
&lt;td&gt;키워드·파일 여러 번 검색하는 불확실한 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;코드베이스 탐색 전용 (읽기·검색 위주)&lt;/td&gt;
&lt;td&gt;&quot;이 기능은 어디에 구현돼 있지?&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;구현 계획 수립 — 단계별 전략 + 주요 파일 식별&lt;/td&gt;
&lt;td&gt;대규모 리팩토링·기능 추가 전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;claude-code-guide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code 자체 기능 Q&amp;#x26;A (훅·슬래시·MCP 등)&lt;/td&gt;
&lt;td&gt;&quot;이 기능 어떻게 설정해?&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;statusline-setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;상태바 커스터마이징&lt;/td&gt;
&lt;td&gt;상태바 설정 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;실전 선택 팁&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;탐색 범위가 명확하지 않다 → &lt;code class=&quot;language-text&quot;&gt;general-purpose&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;그냥 &quot;어디 있는지&quot;만 알면 됨 → &lt;code class=&quot;language-text&quot;&gt;Explore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;큰 변경을 설계해야 함 → &lt;code class=&quot;language-text&quot;&gt;Plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Claude Code 자체 문법·설정 질문 → &lt;code class=&quot;language-text&quot;&gt;claude-code-guide&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;커스텀 Subagent 만들기 — &lt;code class=&quot;language-text&quot;&gt;.claude/agents/&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;프로젝트 루트 혹은 유저 홈에 agent 정의 파일을 두면 Claude Code가 자동 인식합니다.&lt;/p&gt;
&lt;h3&gt;파일 위치와 범위&lt;/h3&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;프로젝트&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;repo&gt;/.claude/agents/&amp;lt;name&gt;.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레포 팀원 전원 (Git)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;~/.claude/agents/&amp;lt;name&gt;.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;본인 전용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;플러그인&lt;/td&gt;
&lt;td&gt;플러그인 설치 시 포함&lt;/td&gt;
&lt;td&gt;플러그인 사용자&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;최소 정의 예시&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;repo&gt;/.claude/agents/security-reviewer.md&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;name: security-reviewer
description: Use when reviewing code for OWASP Top 10, secrets exposure, or auth bugs. Proactively invoke after touching auth/session/input-validation code.
tools: Read, Grep, Glob
model: sonnet&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

You are a security-focused code reviewer.

For any code shown:

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; Scan for OWASP Top 10 patterns (injection, XSS, SSRF, auth bypass).
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; Flag hardcoded secrets, API keys, tokens.
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; Check session/cookie handling, CSRF protection.
&lt;span class=&quot;token list punctuation&quot;&gt;4.&lt;/span&gt; Return a punch list with severity (High / Medium / Low) and file:line citations.

Keep report under 300 words. Don&apos;t lecture — just findings and fixes.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;프론트매터 필드&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;에이전트 식별자 (필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;trigger&lt;/strong&gt; — 메인 Claude가 &quot;언제 이 에이전트를 써야 하는지&quot; 판단 근거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;허용 도구 목록 (생략 시 전체 허용, &lt;strong&gt;반드시 최소 권한 지정 권장&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용 모델 — &lt;code class=&quot;language-text&quot;&gt;opus&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sonnet&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;haiku&lt;/code&gt;, 또는 생략 시 상속&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;좋은 Subagent 설계 원칙 5가지&lt;/h2&gt;
&lt;h3&gt;1) &lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt;에 &quot;when to use&quot;를 명시&lt;/h3&gt;
&lt;p&gt;메인 Claude는 이 문장을 보고 자동으로 해당 에이전트에 작업을 라우팅합니다. &lt;strong&gt;&quot;무엇을 하는지&quot;보다 &quot;언제 불려야 하는지&quot;가 중요&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;description: Use when reviewing PRs for security, performance, or test coverage issues. Proactively invoke after large code changes.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) Tool은 &lt;strong&gt;최소 권한&lt;/strong&gt;으로&lt;/h3&gt;
&lt;p&gt;읽기만 하면 되는 리뷰어에게 &lt;code class=&quot;language-text&quot;&gt;Write&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;Bash&lt;/code&gt;를 열어두면, 에이전트 오작동이 실제 변경으로 이어집니다. blast radius를 좁히세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Grep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Glob &lt;span class=&quot;token comment&quot;&gt;# 리뷰어는 여기까지만&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3) &lt;strong&gt;Self-contained&lt;/strong&gt; 프롬프트&lt;/h3&gt;
&lt;p&gt;에이전트는 메인 세션을 보지 못합니다. &quot;아까 말한 그 함수 있잖아&quot;는 통하지 않습니다. 프롬프트 안에 &lt;strong&gt;필요한 모든 맥락&lt;/strong&gt;(파일 경로, 목표, 제약)을 담으세요.&lt;/p&gt;
&lt;h3&gt;4) &lt;strong&gt;결과 형식&lt;/strong&gt;을 지정&lt;/h3&gt;
&lt;p&gt;자유 형식 에이전트는 메인 context에 쓰레기를 쏟아냅니다. &quot;200자 이내 보고서&quot;, &quot;체크리스트 형식&quot;, &quot;JSON으로만 답변&quot; 같은 제약이 필수.&lt;/p&gt;
&lt;h3&gt;5) &lt;strong&gt;Trust but verify&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;에이전트 리턴값은 &quot;의도&quot;입니다. 실제로 파일이 변경됐다면 Read/git diff로 검증하세요. &quot;했다&quot;와 &quot;진짜 했다&quot;는 다릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/a24df9770bd0aabccfdcdc7fd6e3570b/64756/diagram-lifecycle.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACM0lEQVR42g2RWXKjMBRF2ULsGBvELNAsARKTAJvE2MapdFfvfzdN1alb7+fc+/Gcgy9iWMWoKdSCxIiYRfwLl4vqnvWwqfalui1ms49sgMeQTBEZQzSAot9xjkAUxCDad+Oz0l9SPbjapNn48BbjL7d/1PRLlYWsw3JMuPXF5DML1OTlneOGSqohQ22pDGML5f9yujLx9pu/bv26VFui16vVlRm6ds7b6TzdvXqOl/WCWucUqBTvrdcoN2lhC7bGxQQyG6s71M+ALT4esOwh7WNYxryFzSPkk1s0bqb35RJkJoLmEoowa4K09aLyEpuEL1Dc3KS55E1UGC+p3YCHxMTydobNIavdVDvnuIK4haQraJchm6B7RnZWn3+D8gmqVyBvVBoiDOV1yMxJ2rO0rhhOae1ckhqxHouByCHDU0ZfOdty+gRyDcwb6J9ALlwZUTZC6Ijpk7KutCfef8aV46Ua855IS9QA6Qzpq9hltgXqAfTbr7ZdZtIw2XCuI6pPYjeHT9Yd49LxM03EQMuRVmPOrjl7Y/mDxDtQK6hffr0FYmGi4aoTwkTEfPJxl4+0PYTK8aHBYsy5LYSF9ArZO8FrjNYL+z7p35N4eGTGzBRYY6xDrHf5gNsP1HwE0gF5W/BhB4k9R1p+m+6h23vV31nzRfRKqm9Z31S1EGbjvAlQ68Pay6oDEA5AQ0Jtxkco5lzOuZhwOSM1of1mQ0bHjE4pmVI8xbndHwkS40WVn+ojUP8BJXx8rs6ZfbcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/a24df9770bd0aabccfdcdc7fd6e3570b/a1b3b/diagram-lifecycle.webp 215w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/fc700/diagram-lifecycle.webp 430w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/521f7/diagram-lifecycle.webp 860w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/cbd37/diagram-lifecycle.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/a24df9770bd0aabccfdcdc7fd6e3570b/f7886/diagram-lifecycle.png 215w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/bc750/diagram-lifecycle.png 430w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/3e3fe/diagram-lifecycle.png 860w,
/static/a24df9770bd0aabccfdcdc7fd6e3570b/64756/diagram-lifecycle.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/a24df9770bd0aabccfdcdc7fd6e3570b/3e3fe/diagram-lifecycle.png&apos; alt=&apos;Subagent delegate 플로우 5단계 — 정의·호출·실행·반환·검증과 메인 컨텍스트 토큰 소비 비교&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Subagent delegate 플로우 5단계 — 정의·호출·실행·반환·검증과 메인 컨텍스트 토큰 소비 비교&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시 3개&lt;/h2&gt;
&lt;h3&gt;예시 1) 블로그 톤 검토 에이전트&lt;/h3&gt;
&lt;p&gt;블로그 포스팅 톤이 프로젝트 가이드라인과 일치하는지 검증합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.claude/agents/blog-tone-reviewer.md&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;name: blog-tone-reviewer
description: Use to review draft blog posts for tone consistency. Checks greeting, conclusion-first pattern, SEO elements.
tools: Read, Grep, Glob
model: haiku&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

You review Korean dev-blog drafts against the project&apos;s tone guide.

Check:

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; Opens with &quot;안녕하세요, 자바파커입니다&quot;
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; Uses &quot;~합니다&quot; body (친근하되 가볍지 않음)
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; Conclusion stated early with &quot;결론부터 말씀드리면&quot;
&lt;span class=&quot;token list punctuation&quot;&gt;4.&lt;/span&gt; Has frontmatter with title, description (150자 내), tags
&lt;span class=&quot;token list punctuation&quot;&gt;5.&lt;/span&gt; Closes with a reader question
&lt;span class=&quot;token list punctuation&quot;&gt;6.&lt;/span&gt; Has at least 2 section dividers (---)

Report deviations with line numbers and suggested fixes. Under 200 words.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;호출:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Agent({
  subagent_type: &quot;blog-tone-reviewer&quot;,
  prompt: &quot;Review tistory/post/k8s-understanding.md for tone compliance.&quot;
})&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;예시 2) 보안 리뷰어 (병렬 호출)&lt;/h3&gt;
&lt;p&gt;PR 전체를 세 가지 관점으로 동시 검토.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Agent({ subagent_type: &quot;security-reviewer&quot;, prompt: &quot;Audit PR #42...&quot; })
Agent({ subagent_type: &quot;perf-reviewer&quot;, prompt: &quot;Check perf regression in PR #42...&quot; })
Agent({ subagent_type: &quot;test-coverage-reviewer&quot;, prompt: &quot;Validate test coverage of PR #42...&quot; })&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;한 메시지에 세 개를 동시 호출&lt;/strong&gt; → 셋이 병렬 실행되고 결과가 모여 돌아옵니다. Agent Team과 달리 서로 대화하진 않지만, 단순 리뷰라면 이걸로 충분합니다.&lt;/p&gt;
&lt;h3&gt;예시 3) 리서치용 범용 에이전트 (내장 활용)&lt;/h3&gt;
&lt;p&gt;새 라이브러리 도입 전 조사:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Agent({
  subagent_type: &quot;general-purpose&quot;,
  prompt: &quot;Research pros/cons of using Drizzle ORM vs Prisma for a Next.js 15 project. Summarize under 300 words with a recommendation and two tradeoffs.&quot;
})&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;내장 general-purpose는 WebSearch·WebFetch도 쓸 수 있어 외부 문서 조사까지 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 3가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;에이전트가 내가 방금 말한 걸 모른다&quot;&lt;/h3&gt;
&lt;p&gt;→ 당연합니다. 에이전트는 메인 히스토리를 못 봅니다. 프롬프트에 &lt;strong&gt;직접&lt;/strong&gt; 파일 경로·숫자·제약을 넣으세요.&lt;/p&gt;
&lt;h3&gt;2) &quot;에이전트가 결과를 너무 장황하게 돌려준다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt;·프롬프트에 &quot;Report in under 200 words&quot; 같은 &lt;strong&gt;명시적 길이 제약&lt;/strong&gt;을 넣으세요. 결과가 메인 context로 그대로 들어오기 때문에, 장황하면 오히려 손해입니다.&lt;/p&gt;
&lt;h3&gt;3) &quot;간단한 작업인데 에이전트 쓰니 오히려 느리다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;strong&gt;정확한 판단입니다.&lt;/strong&gt; Subagent는 비용이 있는 선택입니다. 한두 번 &lt;code class=&quot;language-text&quot;&gt;grep&lt;/code&gt;이면 끝날 일은 &lt;strong&gt;직접 수행&lt;/strong&gt;이 빠릅니다. &quot;탐색이 3라운드 이상 될 것 같다&quot; 싶을 때만 delegate하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Subagent와 Skill, Slash Command의 차이는?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skill&lt;/strong&gt;: 메인 세션 안에서 &lt;strong&gt;지식·도구 묶음을 로드&lt;/strong&gt;하는 확장 (예: &lt;code class=&quot;language-text&quot;&gt;update-config&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slash Command&lt;/strong&gt;: 사용자가 타이핑하는 &lt;strong&gt;매크로&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subagent&lt;/strong&gt;: 별도 Claude 세션으로 &lt;strong&gt;위임&lt;/strong&gt;하는 작업 실행자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Skill·Command는 메인 컨텍스트에 머물고, Subagent만 컨텍스트를 분리합니다.&lt;/p&gt;
&lt;h3&gt;Q. 커스텀 에이전트는 어디에 둬야 팀 공유가 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;repo&gt;/.claude/agents/*.md&lt;/code&gt; 에 넣고 &lt;strong&gt;Git에 커밋&lt;/strong&gt;하세요. 같은 레포를 clone한 팀원 전원이 동일 에이전트를 자동 인식합니다.&lt;/p&gt;
&lt;h3&gt;Q. &lt;code class=&quot;language-text&quot;&gt;model: opus&lt;/code&gt;로 지정하면 비쌀텐데, 효과가 있나요?&lt;/h3&gt;
&lt;p&gt;리서치·복잡한 추론·계획 수립 같은 &lt;strong&gt;주제가 크고 깊은 작업&lt;/strong&gt;에는 opus가 효과적입니다. 반면 &lt;strong&gt;형식 검증·패턴 탐지&lt;/strong&gt; 같은 기계적 작업은 &lt;code class=&quot;language-text&quot;&gt;haiku&lt;/code&gt;로도 충분하고 비용이 훨씬 낮습니다. 에이전트별로 model을 다르게 지정하는 게 &lt;strong&gt;토큰 최적화의 핵심 지렛대&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h3&gt;Q. Subagent 안에서 또 Subagent를 호출할 수 있나요?&lt;/h3&gt;
&lt;p&gt;허용되지 않습니다. &lt;strong&gt;중첩 호출은 막혀 있습니다.&lt;/strong&gt; 복잡한 협업이 필요하면 다음 편에서 다룰 &lt;strong&gt;Agent Teams&lt;/strong&gt;를 쓰세요 (팀원 간 통신 가능).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;Subagent는 &quot;Claude Code 숙련도를 한 단계 올리는&quot; 핵심 개념입니다. 쓰는 순간 메인 컨텍스트가 깔끔해지고, 토큰 사용도 훨씬 예측 가능해집니다.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subagent는 &lt;strong&gt;독립 컨텍스트 · 제한 도구 · 결과 반환&lt;/strong&gt;의 3박자&lt;/li&gt;
&lt;li&gt;가치는 &lt;strong&gt;컨텍스트 격리 · 병렬화 · 특화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;.claude/agents/*.md&lt;/code&gt;로 &lt;strong&gt;팀 공유 에이전트&lt;/strong&gt; 제작&lt;/li&gt;
&lt;li&gt;설계 원칙: description trigger · 최소 권한 · self-contained · 결과 형식 · verify&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;탐색 3라운드 이상&lt;/strong&gt; 예상될 때만 delegate&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 편&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;2편: Claude Code Agent Teams — Subagent끼리 서로 통신하는 상위 개념&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agent Teams는 Subagent의 확장판으로, 팀원들이 공유 작업 목록으로 &lt;strong&gt;자체 조율&lt;/strong&gt;하고 &lt;strong&gt;서로 메시지&lt;/strong&gt;를 주고받습니다. 다음 편에서 자세히 다루겠습니다.&lt;/p&gt;
&lt;h3&gt;관련 포스팅&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/beginner-guide-2026/&quot;&gt;Claude Code 입문 가이드 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/token-saving-beginner/&quot;&gt;Claude Code 토큰 절약 — 초급&lt;/a&gt; / &lt;a href=&quot;/token-saving-intermediate/&quot;&gt;중급&lt;/a&gt; / &lt;a href=&quot;/token-saving-advanced/&quot;&gt;고급&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 어떤 커스텀 Subagent를 만들어 쓰고 계신가요? 댓글로 공유해주시면 다음 편에 활용 패턴으로 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Helm 완벽 가이드 — 쿠버네티스 YAML 지옥에서 탈출하는 패키지 매니저]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "dev 용 deployment.yaml, staging 용, prod 용… 같은 YAML을 세 번 복사해서 관리하고 있습니다." 솔직히 저도 그랬습니다. 환경별로 ·이미지 태그·리소스 한 줄만 다른데, 전체 YAML…]]></description><link>https://blog.javapark.kr/helm-package-management/</link><guid isPermaLink="false">https://blog.javapark.kr/helm-package-management/</guid><pubDate>Sat, 18 Apr 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;dev 용 deployment.yaml, staging 용, prod 용… 같은 YAML을 세 번 복사해서 관리하고 있습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. 환경별로 &lt;code class=&quot;language-text&quot;&gt;replicas&lt;/code&gt;·이미지 태그·리소스 한 줄만 다른데, 전체 YAML을 복사해두니 변경이 생길 때마다 세 파일을 다 고쳐야 합니다. 그러다 한 파일만 빼먹고 배포 사고가 한 번씩 터지곤 하죠.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Helm은 이 YAML 중복·분기를 &quot;템플릿 + 값&quot;으로 분리해주는 K8S 패키지 매니저입니다.&lt;/strong&gt; npm이 JavaScript를, apt가 리눅스 패키지를 관리하듯이 Helm은 쿠버네티스 애플리케이션을 관리합니다. 오늘은 3대 개념부터 커스텀 차트 작성·환경별 values 분리·롤백까지, 실전에서 바로 쓰는 수준으로 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt; · &lt;a href=&quot;/kubectl-essential-commands/&quot;&gt;kubectl 실전 명령어&lt;/a&gt; · &lt;a href=&quot;/k9s-essential-guide/&quot;&gt;k9s 완벽 가이드&lt;/a&gt;부터 읽으시면 이번 편이 훨씬 수월합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Helm이란? — 한 줄 정의와 비유&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt;은 &lt;strong&gt;쿠버네티스 애플리케이션을 패키지로 묶어 설치·업그레이드·롤백을 관리&lt;/strong&gt;하는 CNCF 공식 도구입니다.&lt;/p&gt;
&lt;p&gt;비유하자면 이렇습니다.&lt;/p&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;&lt;strong&gt;npm / pip&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JS · Python 라이브러리&lt;/td&gt;
&lt;td&gt;패키지(package)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;apt / brew&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;리눅스 · macOS 프로그램&lt;/td&gt;
&lt;td&gt;패키지(package)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Helm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;쿠버네티스 애플리케이션&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Chart&lt;/strong&gt;(차트)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Chart 하나에 Deployment·Service·ConfigMap 같은 K8S 리소스 묶음 + 환경 변수 + 버전 정보가 들어있습니다. &lt;code class=&quot;language-text&quot;&gt;helm install&lt;/code&gt; 한 줄로 전체 스택이 뜨고, &lt;code class=&quot;language-text&quot;&gt;helm uninstall&lt;/code&gt;로 깔끔히 정리됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3대 핵심 개념 — Chart, Release, Repository&lt;/h2&gt;
&lt;p&gt;Helm을 이해하려면 이 세 단어만 구분하면 됩니다.&lt;/p&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;npm 비유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;K8S 리소스 템플릿 묶음 (설치 단위)&lt;/td&gt;
&lt;td&gt;package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chart를 &lt;strong&gt;클러스터에 설치한 인스턴스&lt;/strong&gt; (이름 붙은 배포)&lt;/td&gt;
&lt;td&gt;설치된 앱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chart 저장소 (공식·사내 모두 가능)&lt;/td&gt;
&lt;td&gt;npm registry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심:&lt;/strong&gt; Chart 하나는 여러 Release로 동시에 존재할 수 있습니다. 같은 &lt;code class=&quot;language-text&quot;&gt;nginx&lt;/code&gt; Chart를 &lt;code class=&quot;language-text&quot;&gt;nginx-dev&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;nginx-prod&lt;/code&gt;로 이름만 다르게 설치하면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;설치 — OS별 한 줄&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# macOS&lt;/span&gt;
brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; helm

&lt;span class=&quot;token comment&quot;&gt;# Windows (scoop)&lt;/span&gt;
scoop &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; helm

&lt;span class=&quot;token comment&quot;&gt;# Windows (winget)&lt;/span&gt;
winget &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; Helm.Helm

&lt;span class=&quot;token comment&quot;&gt;# Linux (바이너리)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설치 확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Helm은 &lt;code class=&quot;language-text&quot;&gt;~/.kube/config&lt;/code&gt;를 그대로 씁니다. kubectl이 되면 Helm도 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 10분 — nginx 차트로 감 잡기&lt;/h2&gt;
&lt;p&gt;공식 레포를 추가하고 nginx를 설치해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1) 레포 추가&lt;/span&gt;
helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; bitnami https://charts.bitnami.com/bitnami
helm repo update

&lt;span class=&quot;token comment&quot;&gt;# 2) 검색&lt;/span&gt;
helm search repo nginx

&lt;span class=&quot;token comment&quot;&gt;# 3) 설치 (release 이름 = my-nginx)&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; my-nginx bitnami/nginx

&lt;span class=&quot;token comment&quot;&gt;# 4) 설치된 릴리스 목록&lt;/span&gt;
helm list

&lt;span class=&quot;token comment&quot;&gt;# 5) 현재 적용된 values 확인&lt;/span&gt;
helm get values my-nginx

&lt;span class=&quot;token comment&quot;&gt;# 6) 업그레이드 (replicas 2개로)&lt;/span&gt;
helm upgrade my-nginx bitnami/nginx &lt;span class=&quot;token parameter variable&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;replicaCount&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 7) 히스토리 보기&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;history&lt;/span&gt; my-nginx

&lt;span class=&quot;token comment&quot;&gt;# 8) 롤백 (직전 버전으로)&lt;/span&gt;
helm rollback my-nginx &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 9) 제거&lt;/span&gt;
helm uninstall my-nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;install&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;upgrade&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;rollback&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;uninstall&lt;/code&gt; 이 네 개가 Helm 라이프사이클의 전부입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/0e452e91732db85d58da86fa51f7f0a9/64756/diagram-lifecycle.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACNUlEQVR42iXRWW7bMBRAUe2gcW3NtCRqoiaKg0SKGi07dpwE/Ujb/e+lDApcEPw5eCCf8eLiA1RWrPx8+JmKE5qsbErxpe4fYTlbqXSLyatmnV8voFmAvqDRy5TOOPjtS7m5zZbKm0WmE7na7aMePvj+O+0eAHVhoyKsEr5BsoblFCAV5gPUPpHGC8Cnbjo2w4+8j6hy6tWWX8dqs8kbGP5IScZlqLmQt8+m2xTbm3pGueL06sW9cQCt3a3nbvfwfESdWS/h/Bf2H26zm+VKhRiXpeQzakVW9aJ7Je0GEwFj7kKuMfarCfHXczEew/aYy3O1FvLdSZWdqajos2ZC7QISFucMxH1aLkEiQECdkBnHgJi1OiJxiNgp66xycvHNxBcTrV59QY0gpEsLlhQ0QSz5ntmHEY8ibofUMCOmsY1Hh0xOO9r16rKnz56g+/TpvWoFprKhoqFdhqjGaSrSRMSwt87UsGJuYWVrSWedjVeXvPn0CfiHR15L3NVtX+kT8zQn2iSxTsCot8A37ly2+Gxx2epqT/ZI/Ar6d8CeXnurSY/ZoHGFeZLRPFcFmpJYwvA/TvpAXkO2ee3ilNLCezl/RezhlJtdLPrBhI1ZwdKCxmHDqo2xewzl2WeWTw03lVY9WI1y8AjVLVufUN6huEf9LequMVsgGWM6B61y+eSyWQ+zY504+dzwcqVXBerl3CwR3SO2h2QL8HpuZlDPnv78cnSRXttgosFCyowHE0orUSfA/wH4Q3oMggi+ywAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/0e452e91732db85d58da86fa51f7f0a9/a1b3b/diagram-lifecycle.webp 215w,
/static/0e452e91732db85d58da86fa51f7f0a9/fc700/diagram-lifecycle.webp 430w,
/static/0e452e91732db85d58da86fa51f7f0a9/521f7/diagram-lifecycle.webp 860w,
/static/0e452e91732db85d58da86fa51f7f0a9/cbd37/diagram-lifecycle.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/0e452e91732db85d58da86fa51f7f0a9/f7886/diagram-lifecycle.png 215w,
/static/0e452e91732db85d58da86fa51f7f0a9/bc750/diagram-lifecycle.png 430w,
/static/0e452e91732db85d58da86fa51f7f0a9/3e3fe/diagram-lifecycle.png 860w,
/static/0e452e91732db85d58da86fa51f7f0a9/64756/diagram-lifecycle.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/0e452e91732db85d58da86fa51f7f0a9/3e3fe/diagram-lifecycle.png&apos; alt=&apos;Helm 릴리스 라이프사이클 4단계 — install · upgrade · rollback · uninstall과 리비전 변화&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Helm 릴리스 라이프사이클 4단계 — install · upgrade · rollback · uninstall과 리비전 변화&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;helm uninstall&lt;/code&gt;은 Release가 만든 모든 리소스를 한 번에 정리합니다. &lt;code class=&quot;language-text&quot;&gt;kubectl delete&lt;/code&gt; 일일이 치는 것보다 훨씬 깔끔합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Chart 구조 — 안을 열어보면&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;helm create my-app&lt;/code&gt; 하면 템플릿 차트가 만들어집니다. 구조는 단순합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;my-app/
├── Chart.yaml          # 메타데이터 (이름·버전·의존성)
├── values.yaml         # 기본값 변수
├── templates/          # YAML 템플릿
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── _helpers.tpl    # 재사용 템플릿 함수
│   └── NOTES.txt       # 설치 후 안내 메시지
└── charts/             # 의존 Chart (subcharts)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;각 파일 역할:&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Chart.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;패키지 정보&lt;/strong&gt; — name, version, appVersion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;values.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;기본 변수값&lt;/strong&gt; — install 시 &lt;code class=&quot;language-text&quot;&gt;--set&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt;로 오버라이드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;templates/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 템플릿 문법이 들어간 &lt;strong&gt;YAML 뼈대&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;_helpers.tpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;include&lt;/code&gt;로 호출하는 &lt;strong&gt;공용 템플릿&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;템플릿 문법 4가지 — 이것만 알면 90%&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;templates/deployment.yaml&lt;/code&gt; 안에 들어가는 Go 템플릿입니다.&lt;/p&gt;
&lt;h3&gt;1) 변수 치환&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; .Release.Name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; .Values.replicaCount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;.Release.Name&lt;/code&gt; — 릴리스 이름 (내장)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;.Values.replicaCount&lt;/code&gt; — values.yaml 의 값&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2) 조건 분기 (&lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt;)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; if .Values.ingress.enabled &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; networking.k8s.io/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Ingress
&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; end &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;enabled: false&lt;/code&gt;면 Ingress 리소스 자체가 렌더링되지 않습니다.&lt;/p&gt;
&lt;h3&gt;3) 반복 (&lt;code class=&quot;language-text&quot;&gt;range&lt;/code&gt;)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; range .Values.env &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; .name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; .value &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; quote &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; end &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;values.yaml:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; LOG_LEVEL
    &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; info
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; NODE_ENV
    &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4) 헬퍼 호출 (&lt;code class=&quot;language-text&quot;&gt;include&lt;/code&gt;)&lt;/h3&gt;
&lt;!-- prettier-ignore --&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; include &quot;my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app.labels&quot; . &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; nindent 4 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;_helpers.tpl&lt;/code&gt;의 공용 라벨 블록을 재사용합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;{{- }}&lt;/code&gt; 앞뒤의 &lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt;는 공백 제거입니다. 안 쓰면 YAML에 빈 줄이 남아 보기 불편합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;환경별 values 분리 패턴 — dev/staging/prod&lt;/h2&gt;
&lt;p&gt;여기가 Helm의 진짜 가치가 드러나는 부분입니다. &lt;strong&gt;같은 Chart, 다른 values&lt;/strong&gt;로 환경을 분리하세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/f38a6f92fec24363842f2feb56cfea56/64756/diagram-anatomy.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOUlEQVR42h2RaY6jMBSEuUHSSdhsDGY3q20wO4SQVdNq9WhGmjPM/W8wzkjfD0vvlcpVT9khug9HM5lA1sN0gNlopaOdz2bc+dmSNY+kusGwt+LBIpOVzCiZrHiE4QDCTtk7bEc3UG2ou9jdJsH1xe3uWnPLh696+03nb7mNyOhlXZAKOxIOaSQgaJSdXbrXFxy3fdaeyv5ImqNVmmSpv/9a1euUbTp9WPkKwn5a1l8/Py/3H1UzpDkDfi2dKa4WLDYtG1TSqqQxvNpMl2D9g9ovQO+Qv6TYivqUTe30LPjoJ3WQCeAxZW8zKxUwak+YH2ymhsL0G0OmyjZIlpNTHx0B0wWGXVo2wzjnJc/zgjFqYKYcHGaSBheL6omDw7WogZFsbiFiI+IK0lUjZ5AuKO69tA6LHocVcgs3KAxMFWl49Gqczppbf1iFFNvJKMVeudrxcJTOWIBkkW8cV04koMt1VECcaw5VVK+SUWE+au/ArZH1VtTB9IzYE5U3PTlrZDHiSRbhJ1VStD6pcEjdsFSlWPNqnY46mww+A7ECtrxPWlxg/Qn4C1Qvk7/eYlf4CScZizMeZTxMmGozRfeFUY4GnUw2G2w28xGFnfynUT70/CrRJEGv2hwHZUTKgDCJF9GjFBtBq+e9UQwS3G/++YmnBx7u3vjEw80RV5tfUHm28hnTGRW9GteSU1TtEVf+dysPM73H7OLwFdGzVS6omK18kiNAeiPu9KjVou4Udnu/2fnNIeg+7OofoU94UJoCalUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/f38a6f92fec24363842f2feb56cfea56/a1b3b/diagram-anatomy.webp 215w,
/static/f38a6f92fec24363842f2feb56cfea56/fc700/diagram-anatomy.webp 430w,
/static/f38a6f92fec24363842f2feb56cfea56/521f7/diagram-anatomy.webp 860w,
/static/f38a6f92fec24363842f2feb56cfea56/cbd37/diagram-anatomy.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/f38a6f92fec24363842f2feb56cfea56/f7886/diagram-anatomy.png 215w,
/static/f38a6f92fec24363842f2feb56cfea56/bc750/diagram-anatomy.png 430w,
/static/f38a6f92fec24363842f2feb56cfea56/3e3fe/diagram-anatomy.png 860w,
/static/f38a6f92fec24363842f2feb56cfea56/64756/diagram-anatomy.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/f38a6f92fec24363842f2feb56cfea56/3e3fe/diagram-anatomy.png&apos; alt=&apos;Helm Chart + Values + Release 구조 — 같은 Chart에 환경별 Values를 주입해 여러 Release로 배포&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Helm Chart + Values + Release 구조 — 같은 Chart에 환경별 Values를 주입해 여러 Release로 배포&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;h3&gt;파일 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;deploy/
├── Chart/              # 공통 Chart
└── values/
    ├── values-dev.yaml
    ├── values-staging.yaml
    └── values-prod.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;values-dev.yaml&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;replicaCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100m
    &lt;span class=&quot;token key atrule&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 128Mi
&lt;span class=&quot;token key atrule&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;values-prod.yaml&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;replicaCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1.2.4
&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 500m
    &lt;span class=&quot;token key atrule&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 512Mi
  &lt;span class=&quot;token key atrule&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000m
    &lt;span class=&quot;token key atrule&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1Gi
&lt;span class=&quot;token key atrule&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; api.example.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;배포 명령&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# dev&lt;/span&gt;
helm upgrade &lt;span class=&quot;token parameter variable&quot;&gt;--install&lt;/span&gt; my-app ./Chart &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values/values-dev.yaml &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt; dev

&lt;span class=&quot;token comment&quot;&gt;# prod (여러 -f 체이닝 가능 — 뒤에 올수록 우선)&lt;/span&gt;
helm upgrade &lt;span class=&quot;token parameter variable&quot;&gt;--install&lt;/span&gt; my-app ./Chart &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values/values-common.yaml &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values/values-prod.yaml &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt; production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심 패턴:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;upgrade --install&lt;/code&gt;은 &lt;strong&gt;없으면 install, 있으면 upgrade&lt;/strong&gt;로 동작합니다. CD 파이프라인에서 이 한 줄이면 충분합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;실전 생산성 팁 5가지&lt;/h2&gt;
&lt;h3&gt;1) &lt;code class=&quot;language-text&quot;&gt;helm template&lt;/code&gt; — 드라이런&lt;/h3&gt;
&lt;p&gt;실제 렌더링될 YAML을 눈으로 확인합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm template my-app ./Chart &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values-prod.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;배포 전에 무엇이 생성될지 검증 가능. CI에 끼우면 배포 사고를 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;h3&gt;2) &lt;code class=&quot;language-text&quot;&gt;helm diff&lt;/code&gt; 플러그인 — 변경점 미리보기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 플러그인 설치 (1회)&lt;/span&gt;
helm plugin &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; https://github.com/databus23/helm-diff

&lt;span class=&quot;token comment&quot;&gt;# 업그레이드 전 diff 확인&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;diff&lt;/span&gt; upgrade my-app ./Chart &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values-prod.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;terraform plan&lt;/code&gt; 같은 역할. &lt;strong&gt;운영 환경에서 필수&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;h3&gt;3) &lt;code class=&quot;language-text&quot;&gt;--atomic&lt;/code&gt; — 실패 시 자동 롤백&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm upgrade &lt;span class=&quot;token parameter variable&quot;&gt;--install&lt;/span&gt; my-app ./Chart &lt;span class=&quot;token parameter variable&quot;&gt;--atomic&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--timeout&lt;/span&gt; 5m&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;업그레이드 중 하나라도 실패하면 자동으로 이전 버전으로 롤백. 사람 개입 없이 안전.&lt;/p&gt;
&lt;h3&gt;4) OCI 레지스트리 사용&lt;/h3&gt;
&lt;p&gt;Helm 3.8+는 Chart를 OCI(도커 레지스트리 표준)에 저장할 수 있습니다. 별도 Chart 서버 필요 없음.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# push&lt;/span&gt;
helm push my-app-0.1.0.tgz oci://registry.example.com/charts

&lt;span class=&quot;token comment&quot;&gt;# install&lt;/span&gt;
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; my-app oci://registry.example.com/charts/my-app &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GHCR·AWS ECR·Harbor 모두 지원합니다.&lt;/p&gt;
&lt;h3&gt;5) &lt;code class=&quot;language-text&quot;&gt;helm lint&lt;/code&gt; — 차트 검증&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm lint ./Chart &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; values-prod.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;문법·필수 필드·관례 위반을 잡아줍니다. CI 첫 스텝에 넣어두는 걸 권장합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Helm vs kustomize — 언제 뭘 쓰나?&lt;/h2&gt;
&lt;p&gt;쿠버네티스 생태계에서 가장 자주 비교되는 두 도구입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Helm&lt;/th&gt;
&lt;th&gt;kustomize&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;방식&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;템플릿 + 변수&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;patch/overlay&lt;/strong&gt; (변수 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습 곡선&lt;/td&gt;
&lt;td&gt;Go 템플릿 문법 필요&lt;/td&gt;
&lt;td&gt;YAML만 알면 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;릴리스 관리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;helm history&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;rollback&lt;/code&gt; 내장&lt;/td&gt;
&lt;td&gt;없음 (kubectl에 의존)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;패키지 공유&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;레포지토리 생태계 풍부&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;빈약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;의존성 관리&lt;/td&gt;
&lt;td&gt;subcharts 지원&lt;/td&gt;
&lt;td&gt;직접 composition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;환경 분리&lt;/td&gt;
&lt;td&gt;values 파일&lt;/td&gt;
&lt;td&gt;overlay 디렉토리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;선택 기준:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;외부 Chart를 많이 쓴다&lt;/strong&gt; → Helm (ingress-nginx, cert-manager, prometheus 등은 거의 Helm Chart)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사내 YAML만 관리한다&lt;/strong&gt; → kustomize (러닝 커브 낮음)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;둘 다 필요하다&lt;/strong&gt; → Helm Chart를 kustomize로 오버레이 (흔한 하이브리드)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음 편에서 kustomize를 따로 다루며 실제 비교 예제를 보여드리겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 3가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;템플릿 결과에 빈 줄이 잔뜩&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;{{- if .. -}}&lt;/code&gt; 처럼 &lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt;로 공백 제거. YAML 파싱 에러의 주요 원인.&lt;/p&gt;
&lt;h3&gt;2) &quot;values 오버라이드가 안 먹힌다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt; 는 뒤에 올수록 우선. &lt;code class=&quot;language-text&quot;&gt;--set&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt;보다 뒤에 적용됨. 확인하려면 &lt;code class=&quot;language-text&quot;&gt;helm get values &amp;lt;release&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;3) &quot;업그레이드 후 Pod가 바뀌지 않음&quot;&lt;/h3&gt;
&lt;p&gt;→ 이미지 태그를 &lt;code class=&quot;language-text&quot;&gt;latest&lt;/code&gt; 로 두면 K8S가 업데이트를 감지하지 못합니다. &lt;strong&gt;항상 구체적 버전 태그 사용&lt;/strong&gt;. 또는 ConfigMap 체크섬을 annotation에 넣어 강제 재생성.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;checksum/config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; include (print $.Template.BasePath &quot;/configmap.yaml&quot;) . &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; sha256sum &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Helm 2와 Helm 3는 뭐가 다른가요?&lt;/h3&gt;
&lt;p&gt;Helm 2의 &lt;code class=&quot;language-text&quot;&gt;Tiller&lt;/code&gt;(클러스터 서버 컴포넌트)가 Helm 3에서 &lt;strong&gt;제거&lt;/strong&gt;되어 보안 이슈와 복잡도가 크게 줄었습니다. 지금 시작한다면 무조건 Helm 3 이상을 쓰세요. Helm 2는 2020년 말 EOL입니다.&lt;/p&gt;
&lt;h3&gt;Q. 운영 환경에서 Helm을 써도 안전한가요?&lt;/h3&gt;
&lt;p&gt;네, 대부분의 운영 환경이 Helm을 씁니다. 다만 &lt;strong&gt;반드시 &lt;code class=&quot;language-text&quot;&gt;helm diff&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;--atomic&lt;/code&gt;을 조합&lt;/strong&gt;하세요. 이 두 장치가 없으면 YAML 수동 관리보다 오히려 위험할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q. Chart 버전 관리는 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Chart.yaml&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;version&lt;/code&gt;(Chart 자체 버전)과 &lt;code class=&quot;language-text&quot;&gt;appVersion&lt;/code&gt;(애플리케이션 버전)을 둘 다 의미있게 올리세요. 일반적으로:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;appVersion 변경&lt;/strong&gt; → 앱 코드 업데이트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;version 변경&lt;/strong&gt; → Chart 템플릿·values 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 개가 분리되어 있으면 &quot;앱은 그대로인데 Chart만 바꾸는&quot; 케이스를 명확히 추적할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q. Chart를 공개 배포하고 싶은데 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;세 가지 옵션이 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub Pages + chart-releaser&lt;/strong&gt; — 무료, 셋업 간단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OCI 레지스트리 (GHCR·Harbor)&lt;/strong&gt; — 컨테이너 이미지와 통합 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifact Hub 등록&lt;/strong&gt; — 검색 노출 (위 2개 중 하나 + 등록)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사내용이면 OCI, 오픈소스면 Artifact Hub 등록까지 해두는 게 표준입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;Helm은 &quot;YAML이 100줄 넘어가는 순간&quot;부터 진가가 드러납니다. 오늘 정리한 핵심만 다시 짚으면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Helm은 &lt;strong&gt;K8S의 패키지 매니저&lt;/strong&gt; — Chart·Release·Repository 3개 개념&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;upgrade --install&lt;/code&gt; 한 줄로 CD 파이프라인 완성&lt;/li&gt;
&lt;li&gt;환경별 &lt;strong&gt;values 파일 분리&lt;/strong&gt;가 진짜 가치&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;helm diff&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;--atomic&lt;/code&gt;이 운영 안전장치&lt;/li&gt;
&lt;li&gt;외부 Chart 생태계(ingress-nginx, cert-manager, prometheus 등)가 풍부함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; kustomize로 환경별 YAML 관리 (Helm 대안 · 조합)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD로 GitOps 배포 파이프라인 (Helm + Git)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prometheus + Grafana 모니터링 스택&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 Helm을 쓰면서 가장 유용했던 패턴이나 반대로 고생했던 지점이 있으신가요? 댓글로 공유해주시면 다음 편에 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[k9s 완벽 가이드 — kubectl보다 10배 빠른 쿠버네티스 TUI 대시보드]]></title><description><![CDATA[…]]></description><link>https://blog.javapark.kr/k9s-essential-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/k9s-essential-guide/</guid><pubDate>Sat, 18 Apr 2026 22:30:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;&lt;code class=&quot;language-text&quot;&gt;kubectl get pods&lt;/code&gt; 치고, &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt; 치고, &lt;code class=&quot;language-text&quot;&gt;logs&lt;/code&gt; 치고… 하루에 같은 명령을 몇 십 번 반복하고 있습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. 쿠버네티스 운영이 익숙해질수록 반복 타이핑이 쌓이고, 멀티 컨텍스트·멀티 네임스페이스를 오갈 때는 &lt;code class=&quot;language-text&quot;&gt;kubectl config use-context&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;-n namespace&lt;/code&gt; 옵션 붙이는 것만으로도 피곤해집니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;k9s는 이 반복을 키보드 몇 개로 줄여주는 쿠버네티스 TUI 대시보드입니다.&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;~/.kube/config&lt;/code&gt;만 있으면 바로 동작하고, Pod 목록·로그·exec·port-forward를 전부 한 화면에서 처리합니다. 오늘은 설치부터 실전 워크플로우, 커스터마이징까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt;와 &lt;a href=&quot;/kubectl-essential-commands/&quot;&gt;kubectl 실전 명령어 가이드&lt;/a&gt;부터 읽고 오시면 흐름이 이어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;k9s란?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://k9scli.io/&quot;&gt;k9s&lt;/a&gt;는 터미널에서 동작하는 &lt;strong&gt;쿠버네티스 TUI(Text UI) 대시보드&lt;/strong&gt;입니다. Go로 작성된 단일 바이너리이고, 오픈소스(Apache 2.0)입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;~/.kube/config&lt;/code&gt;를 그대로 사용 — kubectl이 되면 k9s도 됩니다&lt;/li&gt;
&lt;li&gt;실시간 리프레시 (기본 2초 간격)&lt;/li&gt;
&lt;li&gt;키보드 단축키 기반 내비게이션&lt;/li&gt;
&lt;li&gt;로그 스트리밍·exec·port-forward·edit 모두 한 화면에서&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심 장점:&lt;/strong&gt; &quot;어떤 리소스가 어떤 상태인지&quot; 한눈에 보여주기 때문에, 디버깅과 탐색이 압도적으로 빠릅니다. kubectl이 &quot;정밀한 외과용 칼&quot;이라면 k9s는 &quot;넓게 스캔하는 관제실 모니터&quot;에 가깝습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;설치 — OS별 한 줄 명령어&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# macOS&lt;/span&gt;
brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; derailed/k9s/k9s

&lt;span class=&quot;token comment&quot;&gt;# Linux (snap)&lt;/span&gt;
snap &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; k9s &lt;span class=&quot;token parameter variable&quot;&gt;--devmode&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Linux (바이너리 직접 다운로드)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-sS&lt;/span&gt; https://webinstall.dev/k9s &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Windows (scoop)&lt;/span&gt;
scoop &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; k9s

&lt;span class=&quot;token comment&quot;&gt;# Windows (winget)&lt;/span&gt;
winget &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; k9s&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설치 후 확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;k9s version
&lt;span class=&quot;token comment&quot;&gt;# 또는 바로 실행&lt;/span&gt;
k9s&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;k9s가 뜨면 현재 컨텍스트의 리소스가 보입니다. 설정 끝.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;주의:&lt;/strong&gt; k9s는 현재 컨텍스트를 그대로 사용합니다. 실수로 운영 클러스터에서 실행하지 않도록 &lt;code class=&quot;language-text&quot;&gt;kubectl config current-context&lt;/code&gt;로 먼저 확인하는 습관을 들이세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;화면 구성 — 3줄로 끝&lt;/h2&gt;
&lt;p&gt;k9s는 UI가 단순합니다.&lt;/p&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;&lt;strong&gt;상단 헤더&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;현재 컨텍스트·클러스터·네임스페이스·단축키 힌트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;리스트 뷰&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;현재 조회 중인 리소스(Pod, Deployment 등) 테이블&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;커맨드바&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:&lt;/code&gt;를 눌러 리소스 전환 (&lt;code class=&quot;language-text&quot;&gt;:pod&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;:svc&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;:ns&lt;/code&gt; …)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;리스트 항목을 화살표로 선택하고, 단축키 한 글자로 액션을 실행하는 흐름입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;필수 단축키 20개 — 외우면 끝&lt;/h2&gt;
&lt;p&gt;k9s는 단축키가 전부입니다. 이 표만 외우면 일상의 90%가 커버됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/3d7d0991a109bf058fbe82ca475652dc/64756/diagram-shortcuts.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVR42iXS2W6jMBQGYN6gzULZw2psDN4g7JCQhdB0G41Uzbz/m4zTkf6LI8ufz5Ft5clhq7hfwV6Lq1icXNQBekzEGfLJCEojLE1QW3FjwcZGnY16GQu2JpCplWeXrfPLih511tPDO9jPuH0n3QcbPg3YOfFgx8MOjl4y+XiISe0nlYyHSjMqH3i7b63qoIlhk9XPqHWqD0fc1PRk0kuEb6z8TYpfiH3F6dg1RV51KS1kzLBQVjum0iYcF7e5yGIFKth+ZdO3zmY9OwXoWvZ/yuFvhO8R6rig3eHCixYmzAyEsnb5Bld+c91VZ5XU67jxyzfQfRlslp09eJKdU/4pcYi6jJC87AkrJTb8B2bbrIaHu1c/8BY2YfOZTt8Gv5n0KsdO+Ackb2HyGsA2JSIlBcJcYt1jiuoJNWsN3hus12irZYMpFovfrPzV4nOQzBFewkQWi8QopRDzGHOA2MuOKS9B/kJajXY672U0cjD5YonFLu4muwaJbL7IIwJ480EDMYNY/GCu/uDiRw56Phr5oNOjld/t4vWB6cVHlxDf5LX5cPb+4zQHWEQPzBUt3OuklY/8CO2M7GCxxaSzQWcNTy6YfHD2wMkNz05QAphFiEkZQLaRWI8qLWtk3PoUDFdzfwy6W9DO3v7siKNDRo9NTjba6ShrO2ts0qow38b5syOUx9fDnZX2Ljt6YnLouGOHHZW7e7luJo2Bah3WGihVUG9BvQH1U1iuo3q1y/8BABN+M88ZXR4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/3d7d0991a109bf058fbe82ca475652dc/a1b3b/diagram-shortcuts.webp 215w,
/static/3d7d0991a109bf058fbe82ca475652dc/fc700/diagram-shortcuts.webp 430w,
/static/3d7d0991a109bf058fbe82ca475652dc/521f7/diagram-shortcuts.webp 860w,
/static/3d7d0991a109bf058fbe82ca475652dc/cbd37/diagram-shortcuts.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/3d7d0991a109bf058fbe82ca475652dc/f7886/diagram-shortcuts.png 215w,
/static/3d7d0991a109bf058fbe82ca475652dc/bc750/diagram-shortcuts.png 430w,
/static/3d7d0991a109bf058fbe82ca475652dc/3e3fe/diagram-shortcuts.png 860w,
/static/3d7d0991a109bf058fbe82ca475652dc/64756/diagram-shortcuts.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/3d7d0991a109bf058fbe82ca475652dc/3e3fe/diagram-shortcuts.png&apos; alt=&apos;k9s 핵심 단축키 치트시트 — 이동·조작·뷰·시스템 4개 영역&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;k9s 핵심 단축키 치트시트 — 이동·조작·뷰·시스템 4개 영역&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;h3&gt;1) 리소스 이동 (커맨드모드 &lt;code class=&quot;language-text&quot;&gt;:&lt;/code&gt;)&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;:pod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pod 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deployment 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:svc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:ing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ingress 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:ns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Namespace 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:ctx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Context 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클러스터 이벤트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:node&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2) Pod/리소스 조작&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;describe (Events까지 한 화면에)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;logs (실시간 스트리밍)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;직전 컨테이너 로그 (previous)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;shell — exec -it bash/sh 진입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;port-forward (로컬 포트 프롬프트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;edit (YAML 인라인 수정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;YAML 뷰 (읽기 전용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl+K&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;kill (리소스 삭제)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3) 뷰 제어&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;필터 — 이름으로 실시간 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;뒤로 가기 · 필터 해제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt;~&lt;code class=&quot;language-text&quot;&gt;9&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;네임스페이스 숫자 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 단축키 도움말&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;다중 선택 (Ctrl+D 로 일괄 삭제)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;4) XRay 뷰 (리소스 관계 시각화)&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;:xray pod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pod → Container → Volume → ConfigMap 트리 구조로 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;xray&lt;/code&gt;는 한 번 써보면 빠져나올 수 없습니다. &quot;이 Deployment가 쓰는 ConfigMap이 뭐지?&quot;를 클릭 한 번(&lt;code class=&quot;language-text&quot;&gt;Enter&lt;/code&gt;)으로 드릴다운합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 워크플로우 — &quot;Pod가 죽었을 때&quot;&lt;/h2&gt;
&lt;p&gt;kubectl 편에서 본 5단계 디버깅을 k9s로 하면 &lt;strong&gt;총 10초&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/1978a91b9f6dd0452c9da8a18d7ef4bd/64756/diagram-workflow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHklEQVR42kWS6a6kIBSEfYPebFTAfWt3tAVBxaW7c+fOZN7/eQZzf0xSqfCD4jungnaym3M66Y/Rr2VQLV42wKgDce8VMus+fi6tkKGY40TYj9F5TMpxzFHEYcy1s9uA5gPJuxK/Kv5d0jlqqdtOhH0x+TfuX2YucDXj8r9QM1vFqB7VLk7t55NfzFbEDoWDUhKzKuVu2JvV6NEXJovK2/UCM2HkHFAJyGSEvXZ1GhgPKOFhuYblYga94XW6Q252awQ9IrPTbX7/drsdNdIsR5ANIB1AxkHw1NS9H2bcbCl5BYXE8WC4neE9YdBDuuLpEy5/bPGx6GbSzehXi+1Wt9y9Vrs5BMYMqnC9qXxULjjhwO2UrLBHbMPiE8lvb/qy+s1QGTKbz9Uks+4S7e4SGA2quqTZHu1LwdP2pbB3rzvIbEPiHcnfiO1GJ0EndSKN5wqIvDpEA14Lf8au1rg6Jvey+e60B1mFnwtkuyc+mO6gmRVWV97Kez1dnVYz/CdKjsKCconqLW3fcbNb8fHcsU45WvWsejJLcS8O6bm45eKa8avbaqpenAxqT3T4cVBup9xOBAwpSJmhin0Mesp05dlwxDJ+SYezIh9V59wqBCwFqkbczJF4O2LHbPXZ7ouXx3ePb86w6cVwSenlwU5KCTupMFTMfETFZJfSriSupNMsuJGwmnA5oWJEuVB/w3xwPaK3gF5Ceg7pLeIXp/sHcC93eLThK80AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/1978a91b9f6dd0452c9da8a18d7ef4bd/a1b3b/diagram-workflow.webp 215w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/fc700/diagram-workflow.webp 430w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/521f7/diagram-workflow.webp 860w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/cbd37/diagram-workflow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/1978a91b9f6dd0452c9da8a18d7ef4bd/f7886/diagram-workflow.png 215w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/bc750/diagram-workflow.png 430w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/3e3fe/diagram-workflow.png 860w,
/static/1978a91b9f6dd0452c9da8a18d7ef4bd/64756/diagram-workflow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/1978a91b9f6dd0452c9da8a18d7ef4bd/3e3fe/diagram-workflow.png&apos; alt=&apos;kubectl vs k9s Pod 디버깅 작업 속도 비교 — 90초 vs 10초&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;kubectl vs k9s Pod 디버깅 작업 속도 비교 — 90초 vs 10초&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. k9s 실행                        → k9s
2. Pod 화면 이동                   → :pod
3. 문제 Pod 필터                   → / nginx
4. describe 열기                   → d     (Events 즉시 확인)
5. 로그 보기                       → l
6. 직전 로그 보기 (Crash인 경우)   → p
7. 컨테이너 진입 (필요 시)         → s&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;kubectl로 같은 작업을 하면 7~8개 명령을 쳐야 하는데, k9s는 &lt;strong&gt;키보드 7번&lt;/strong&gt;입니다. 디버깅 루프의 마찰이 크게 줄어듭니다.&lt;/p&gt;
&lt;h3&gt;포트포워딩 실전&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;f&lt;/code&gt; 하나로 끝납니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;:svc&lt;/code&gt; → Service 목록&lt;/li&gt;
&lt;li&gt;원하는 Service 선택 → &lt;code class=&quot;language-text&quot;&gt;f&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;로컬 포트 입력 (예: &lt;code class=&quot;language-text&quot;&gt;8080&lt;/code&gt;) → 엔터&lt;/li&gt;
&lt;li&gt;자동으로 port-forward 시작, 브라우저에서 &lt;code class=&quot;language-text&quot;&gt;localhost:8080&lt;/code&gt; 접속&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;종료도 &lt;code class=&quot;language-text&quot;&gt;Ctrl+C&lt;/code&gt; 한 번. 포트포워딩 창을 따로 관리할 필요가 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;커스터마이징 — 나에게 맞게 튜닝&lt;/h2&gt;
&lt;h3&gt;1) Skin (테마)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;~/.config/k9s/skins/&lt;/code&gt; 에 YAML 추가하면 색상 테마 변경 가능합니다. 인기 스킨:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;dracula&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;one-dark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;solarized-dark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;nord&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/derailed/k9s/tree/master/skins&quot;&gt;k9s skins 레포&lt;/a&gt;에 공식 프리셋이 있습니다.&lt;/p&gt;
&lt;h3&gt;2) 리소스 별칭 (aliases)&lt;/h3&gt;
&lt;p&gt;자주 쓰는 리소스는 단축어로 만들 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;~/.config/k9s/aliases.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;pp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1/pods
  &lt;span class=&quot;token key atrule&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1/deployments
  &lt;span class=&quot;token key atrule&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1/secrets&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;:pp&lt;/code&gt; 로 Pod 목록 이동이 됩니다.&lt;/p&gt;
&lt;h3&gt;3) 핫키 (hotkeys)&lt;/h3&gt;
&lt;p&gt;Function 키에 액션을 바인딩할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;~/.config/k9s/hotkeys.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;hotKeys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;shift-1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;shortCut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Shift&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;View pods in prod ns&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pod -n production&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Shift+1&lt;/code&gt; 한 번에 prod 네임스페이스 Pod로 이동합니다. 반복 작업이 많다면 적극 활용하세요.&lt;/p&gt;
&lt;h3&gt;4) 플러그인 — stern, jq 연동 예시&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;~/.config/k9s/plugins.yaml&lt;/code&gt;에 외부 도구 바인딩 가능. 예: Pod 선택 상태에서 &lt;code class=&quot;language-text&quot;&gt;Shift+L&lt;/code&gt;을 누르면 stern으로 멀티 Pod 로그 스트리밍.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;shortCut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Shift&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;L
    &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Logs with stern&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;scopes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; po
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; stern
    &lt;span class=&quot;token key atrule&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tail
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;50&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $FILTER&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;kubectl vs k9s — 언제 뭘 쓰나?&lt;/h2&gt;
&lt;p&gt;둘 다 써야 합니다. 역할이 다릅니다.&lt;/p&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;&lt;strong&gt;탐색·디버깅&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;k9s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실시간 뷰, 키 몇 개로 끝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자동화 스크립트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;파이프·jsonpath·exit code 처리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD 파이프라인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;비대화형, 재현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;장애 대응(온콜)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;k9s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;빠른 Pod·로그·이벤트 스캔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YAML 배포·apply&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;선언적 상태 관리 원칙&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;멀티 클러스터 모니터링&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;k9s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;:ctx&lt;/code&gt; 로 즉시 전환 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;현실적 조합:&lt;/strong&gt; 평소 모니터링·디버깅은 k9s, 배포·자동화는 kubectl. 실제 운영자들이 쓰는 패턴입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;자주 겪는 이슈 3가지&lt;/h2&gt;
&lt;h3&gt;1) &quot;context를 못 찾는다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;kubectl config current-context&lt;/code&gt; 먼저 확인. k9s는 kubectl과 같은 설정을 씁니다.&lt;/p&gt;
&lt;h3&gt;2) &quot;네임스페이스가 자꾸 default로 돌아간다&quot;&lt;/h3&gt;
&lt;p&gt;→ k9s 실행 시 &lt;code class=&quot;language-text&quot;&gt;-n &amp;lt;ns&gt;&lt;/code&gt;로 고정. 또는 &lt;code class=&quot;language-text&quot;&gt;~/.config/k9s/config.yaml&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;currentNamespace&lt;/code&gt; 지정.&lt;/p&gt;
&lt;h3&gt;3) &quot;리소스가 너무 많아서 느리다&quot;&lt;/h3&gt;
&lt;p&gt;→ &lt;code class=&quot;language-text&quot;&gt;refreshRate&lt;/code&gt;를 늘려서 리프레시 주기 조정 (기본 2초 → 5~10초). 대형 클러스터에서 권장.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ~/.config/k9s/config.yaml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;k9s&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;refreshRate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;maxConnRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;enableMouse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. 운영 환경에서 k9s 써도 되나요?&lt;/h3&gt;
&lt;p&gt;읽기(조회·로그)는 문제없습니다. 단, &lt;code class=&quot;language-text&quot;&gt;Ctrl+K&lt;/code&gt;(삭제)·&lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;(edit)·&lt;code class=&quot;language-text&quot;&gt;s&lt;/code&gt;(shell) 같은 쓰기 액션은 &lt;strong&gt;RBAC로 제한&lt;/strong&gt;하는 걸 권장합니다. 실수로 Pod를 지우는 걸 방지하기 위해, 운영 컨텍스트는 &lt;code class=&quot;language-text&quot;&gt;ClusterRole&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;view&lt;/code&gt; 정도로 바인딩해두세요.&lt;/p&gt;
&lt;h3&gt;Q. Lens나 Headlamp 같은 GUI는 어떤가요?&lt;/h3&gt;
&lt;p&gt;k9s는 터미널·단축키 중심, Lens/Headlamp는 GUI 중심입니다. SSH 세션 안에서 쓸 거라면 k9s가 거의 유일한 선택입니다. 로컬 개발 중심이면 GUI 도구도 좋은 선택인데, 키보드만으로 속도를 내고 싶다면 k9s가 훨씬 빠릅니다.&lt;/p&gt;
&lt;h3&gt;Q. kubectl 안 써도 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;아니요.&lt;/strong&gt; 자동화·CI/CD·apply 기반 GitOps에서는 여전히 kubectl이 기본입니다. k9s는 &lt;strong&gt;탐색·디버깅 레이어&lt;/strong&gt;라고 생각하세요. 둘 다 있어야 온전합니다.&lt;/p&gt;
&lt;h3&gt;Q. 로그를 파일로 저장하고 싶어요.&lt;/h3&gt;
&lt;p&gt;로그 뷰에서 &lt;code class=&quot;language-text&quot;&gt;s&lt;/code&gt; 키로 저장 가능합니다. 기본 저장 경로는 &lt;code class=&quot;language-text&quot;&gt;/tmp&lt;/code&gt; 하위. 또는 plugin에 &lt;code class=&quot;language-text&quot;&gt;kubectl logs ... &gt; file.log&lt;/code&gt; 명령을 바인딩하는 방법도 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;k9s는 &quot;도입 후 kubectl 쓰는 시간이 절반으로 줄었다&quot;는 후기가 가장 많은 도구입니다. 체감 생산성 변화가 크기 때문에, &lt;strong&gt;오늘 바로 설치해서 30분만 써보세요.&lt;/strong&gt; 단축키 &lt;code class=&quot;language-text&quot;&gt;?&lt;/code&gt; 한 번 누르고 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;l&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;f&lt;/code&gt; 세 개만 익혀도 효과가 즉시 나타납니다.&lt;/p&gt;
&lt;p&gt;오늘 정리 핵심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k9s는 &lt;strong&gt;실시간 TUI 대시보드&lt;/strong&gt;, kubectl과 역할 분담&lt;/li&gt;
&lt;li&gt;단축키 20개 중 &lt;code class=&quot;language-text&quot;&gt;d / l / p / s / f / /&lt;/code&gt; 이 6개가 하루의 80%&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;:ctx&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;:ns&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;:xray&lt;/code&gt;는 멀티 클러스터 운영자의 필수&lt;/li&gt;
&lt;li&gt;커스터마이징(aliases·hotkeys·plugin)으로 반복 작업을 지속 줄이세요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Helm으로 K8S 패키지 관리하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD로 GitOps 배포 파이프라인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; kustomize로 환경별 YAML 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 어떤 k9s 단축키나 플러그인을 가장 유용하게 쓰고 계신가요? 댓글로 공유해주시면 다음 편에 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[kubectl 실전 명령어 완벽 가이드 — 쿠버네티스 입문자가 외워야 할 30개]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "까지는 외웠는데, 막상 Pod가 죽었을 때 뭘 쳐야 할지 막막합니다." 솔직히 저도 그랬습니다. K8S…]]></description><link>https://blog.javapark.kr/kubectl-essential-commands/</link><guid isPermaLink="false">https://blog.javapark.kr/kubectl-essential-commands/</guid><pubDate>Sat, 18 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;&lt;code class=&quot;language-text&quot;&gt;kubectl get pods&lt;/code&gt;까지는 외웠는데, 막상 Pod가 죽었을 때 뭘 쳐야 할지 막막합니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. K8S 공식 문서를 열면 명령어가 수십 개 나오는데, 어떤 걸 먼저 익혀야 할지 순서가 없어서 금방 지칩니다. &quot;외워야 한다&quot;는 부담감에 실전에서는 결국 구글링으로 돌아가게 되죠.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;kubectl은 5개 카테고리, 30개 명령어만 외우면 일상 운영의 90%가 커버됩니다.&lt;/strong&gt; 조회·배포·롤아웃·디버깅·컨텍스트. 이 5개 축으로 정리하면 새 명령어를 만났을 때도 어디에 속하는지 바로 감이 옵니다. 오늘은 이 순서대로, 실전에서 바로 쓰는 패턴까지 정리하겠습니다.&lt;/p&gt;
&lt;p&gt;이전 편을 안 보셨다면 &lt;a href=&quot;/k8s-understanding/&quot;&gt;K8S에 대한 이해&lt;/a&gt;부터 읽어오시면 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/0ec642254d726b7cd122f37738856bd4/64756/diagram-categories.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQklEQVR42iWS266bMBBF+YMmJGCCgXCzDTYXc8c4QCDXo54eVX3qQ///O4paaT1szWhrzcMo3+x8j6VK5N6rIRGEX302Er6w+hGmkxE0JupM3G8rK5J2PNiRsIiAqN/mys7JVb4c8uWQTDZfssuPuH1n8nsz/mTV20LSIdKLW591YSLOVMJoguSycQpbZefmaifVrNPzXk3Gffl5jKYjm33yEbGvkL4Qu82yqOqWlx3Kr17/x0oe5/RlBJ2iuhy0s5ZLlTZaOsH265SsOp2j7DMtf/nxwyezlF1Zi5iVPpOh+O2kTytegVcr6pnrqA34AuNeRa0VTbh4ACzNcAjZEufPMxIeaSifUdzYqDaRpNXLIpN2rpSDy3XSetkMcHPEnUVnP1t1JC00IraS9O6EwicVSWVA6n/li42l7naaWylHt9BoDxKh006n8pSuZnbbLnfjFdEnYm+PTJjWjA8kLhzcnMhV84QWyINTKbpX6qkA+WDwAaTyxJ9m8TKLp0/vOPkI6YcfzXFa0rRgCbdJC6L7Kb4BsqpOqeh+BbKteTEKCdLR5C9YvmHx9Oht0wabOZripIiTMmL5/zKIbjpe1M0MgtrIhJFvTmkkI8yfcDNndwfNPr75+H72BxJlEeUkzqyw1vCq4/WIrnu7UoywBUlvpMLIBjOTZj6Z2Qj5GIpHMDxhu7jtcs6FgQtAyiNu9rjfk36Hup1dKHB7PTpAJu1ktNONy5YhG+zsAlNpMHGiApDuGNSHoNlQg3q35bDdO+Vfzp96ibZozgcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/0ec642254d726b7cd122f37738856bd4/a1b3b/diagram-categories.webp 215w,
/static/0ec642254d726b7cd122f37738856bd4/fc700/diagram-categories.webp 430w,
/static/0ec642254d726b7cd122f37738856bd4/521f7/diagram-categories.webp 860w,
/static/0ec642254d726b7cd122f37738856bd4/cbd37/diagram-categories.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/0ec642254d726b7cd122f37738856bd4/f7886/diagram-categories.png 215w,
/static/0ec642254d726b7cd122f37738856bd4/bc750/diagram-categories.png 430w,
/static/0ec642254d726b7cd122f37738856bd4/3e3fe/diagram-categories.png 860w,
/static/0ec642254d726b7cd122f37738856bd4/64756/diagram-categories.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/0ec642254d726b7cd122f37738856bd4/3e3fe/diagram-categories.png&apos; alt=&apos;kubectl 5개 카테고리와 30개 핵심 명령어 맵 — 조회·배포·롤아웃·디버깅·컨텍스트&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;kubectl 5개 카테고리와 30개 핵심 명령어 맵 — 조회·배포·롤아웃·디버깅·컨텍스트&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;kubectl이란?&lt;/h2&gt;
&lt;p&gt;쿠버네티스를 사용하는 공식 CLI입니다. 본질은 단순합니다 — &lt;strong&gt;K8S API Server에 REST 요청을 보내는 클라이언트&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단일 바이너리, 크로스 플랫폼 (macOS·Linux·Windows)&lt;/li&gt;
&lt;li&gt;설정 파일: &lt;code class=&quot;language-text&quot;&gt;~/.kube/config&lt;/code&gt; (여러 클러스터 전환 가능)&lt;/li&gt;
&lt;li&gt;서버 버전과 클라이언트 버전은 마이너 1개 차이 이내 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 버전 확인 — 가장 먼저 치는 명령어&lt;/span&gt;
kubectl version &lt;span class=&quot;token parameter variable&quot;&gt;--short&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 현재 연결된 클러스터 확인&lt;/span&gt;
kubectl cluster-info&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;기본 명령 구조&lt;/h2&gt;
&lt;p&gt;모든 kubectl 명령은 같은 패턴을 따릅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl &amp;lt;동사&gt; &amp;lt;리소스&gt; &amp;lt;이름&gt; [옵션]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get pod nginx &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; yaml
&lt;span class=&quot;token comment&quot;&gt;#        ──┬── ─┬─ ──┬── ───┬───&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#         동사 리소스 이름   옵션&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;리소스 약어 — 외우면 타이핑이 절반&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;po&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;svc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ingresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;cm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;configmaps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;namespaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;no&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;replicasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;pvc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;persistentvolumeclaims&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl api-resources&lt;/code&gt; 로 전체 약어 목록을 볼 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 조회(Read) — 가장 많이 쓰는 5개&lt;/h2&gt;
&lt;p&gt;K8S 운영의 80%는 &quot;지금 뭐가 돌고 있지?&quot;를 확인하는 일입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 전체 Pod 상태&lt;/span&gt;
kubectl get pods
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; wide          &lt;span class=&quot;token comment&quot;&gt;# IP·노드 정보까지&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; kube-system   &lt;span class=&quot;token comment&quot;&gt;# 특정 네임스페이스&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx     &lt;span class=&quot;token comment&quot;&gt;# 라벨 필터&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt;               &lt;span class=&quot;token comment&quot;&gt;# 모든 네임스페이스&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;--watch&lt;/span&gt;          &lt;span class=&quot;token comment&quot;&gt;# 실시간 변화 관찰&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 상세 정보 (이벤트·조건·연결 확인)&lt;/span&gt;
kubectl describe pod &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 로그&lt;/span&gt;
kubectl logs &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;              &lt;span class=&quot;token comment&quot;&gt;# 실시간 팔로우&lt;/span&gt;
kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;--previous&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;# 직전 종료된 컨테이너 로그&lt;/span&gt;
kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;--tail&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;# 마지막 100줄&lt;/span&gt;
kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx --all-containers  &lt;span class=&quot;token comment&quot;&gt;# 라벨 기반 일괄 조회&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 리소스 사용량 (metrics-server 필요)&lt;/span&gt;
kubectl &lt;span class=&quot;token function&quot;&gt;top&lt;/span&gt; pods
kubectl &lt;span class=&quot;token function&quot;&gt;top&lt;/span&gt; nodes

&lt;span class=&quot;token comment&quot;&gt;# YAML 원본 추출&lt;/span&gt;
kubectl get deployment nginx &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt;의 맨 아래 &lt;strong&gt;Events&lt;/strong&gt; 섹션을 먼저 읽으세요. Pod가 안 뜨는 이유 80%는 여기에 나옵니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;2. 배포(Write) — apply 하나로 대부분 해결&lt;/h2&gt;
&lt;p&gt;선언형(&lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;)과 명령형(&lt;code class=&quot;language-text&quot;&gt;create&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt;)이 있는데, &lt;strong&gt;실무에선 거의 &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;만 씁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 선언적 배포 — YAML을 원천으로&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; deployment.yaml
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; ./k8s/              &lt;span class=&quot;token comment&quot;&gt;# 디렉토리 전체&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-k&lt;/span&gt; ./overlays/prod     &lt;span class=&quot;token comment&quot;&gt;# Kustomize 오버레이&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 삭제&lt;/span&gt;
kubectl delete &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; deployment.yaml
kubectl delete pod &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
kubectl delete pods &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;# 네임스페이스 내 Pod 일괄&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 즉석 편집 (배포본 수정 → 저장 시 반영)&lt;/span&gt;
kubectl edit deployment nginx

&lt;span class=&quot;token comment&quot;&gt;# 스케일&lt;/span&gt;
kubectl scale deployment nginx &lt;span class=&quot;token parameter variable&quot;&gt;--replicas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 이미지만 빠르게 교체 (CD 파이프라인에서 자주 사용)&lt;/span&gt;
kubectl &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; image deployment/nginx &lt;span class=&quot;token assign-left variable&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx:1.28&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;선언형 vs 명령형 선택 기준&lt;/h3&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;운영 환경, GitOps&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;선언형(apply)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빠른 테스트·실험&lt;/td&gt;
&lt;td&gt;명령형(run·create)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리뷰·버전 관리 필요&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;선언형(apply)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;실험용으로 명령형 쓸 때도 &lt;code class=&quot;language-text&quot;&gt;--dry-run=client -o yaml&lt;/code&gt;로 YAML 뽑아서 저장해두는 습관을 들이면 좋습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create deployment demo &lt;span class=&quot;token parameter variable&quot;&gt;--image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx --dry-run&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;client &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; yaml &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; demo.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;3. 롤아웃 관리 — 배포 추적과 롤백&lt;/h2&gt;
&lt;p&gt;Deployment를 업데이트할 때 가장 쓸모 있는 명령들입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 배포 진행 상태 확인 (CD 파이프라인 체크용)&lt;/span&gt;
kubectl rollout status deployment/nginx

&lt;span class=&quot;token comment&quot;&gt;# 배포 히스토리&lt;/span&gt;
kubectl rollout &lt;span class=&quot;token function&quot;&gt;history&lt;/span&gt; deployment/nginx
kubectl rollout &lt;span class=&quot;token function&quot;&gt;history&lt;/span&gt; deployment/nginx &lt;span class=&quot;token parameter variable&quot;&gt;--revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 특정 리비전 상세&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 롤백&lt;/span&gt;
kubectl rollout undo deployment/nginx                  &lt;span class=&quot;token comment&quot;&gt;# 직전 버전&lt;/span&gt;
kubectl rollout undo deployment/nginx --to-revision&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 특정 버전&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 재시작 (ConfigMap 변경 후 Pod 재생성 강제)&lt;/span&gt;
kubectl rollout restart deployment/nginx

&lt;span class=&quot;token comment&quot;&gt;# 진행 중인 배포 일시정지 / 재개&lt;/span&gt;
kubectl rollout pause deployment/nginx
kubectl rollout resume deployment/nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;실전 팁:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;rollout restart&lt;/code&gt;는 Secret/ConfigMap 변경 시 &lt;code class=&quot;language-text&quot;&gt;imagePullPolicy: Always&lt;/code&gt;가 아니어도 Pod를 새로 뜨게 해줍니다. 배포 YAML 수정 없이 Pod만 리프레시할 때 유용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;4. 디버깅 — 여기서 진짜 실력이 나옵니다&lt;/h2&gt;
&lt;p&gt;Pod가 안 뜨거나 이상하게 동작할 때 쓰는 명령들. 입문자가 가장 어려워하는 영역이지만, 패턴 몇 개만 익히면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 컨테이너 안으로 진입&lt;/span&gt;
kubectl &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; -- &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;
kubectl &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; -- &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;# bash 없는 이미지용&lt;/span&gt;
kubectl &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; -- &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;# 환경변수만 출력&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 로컬 포트 ↔ Pod/Service 연결 (브라우저에서 테스트)&lt;/span&gt;
kubectl port-forward svc/nginx &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;:80
kubectl port-forward pod/nginx-xxxx &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;:80

&lt;span class=&quot;token comment&quot;&gt;# 파일 전송&lt;/span&gt;
kubectl &lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; ./local.txt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;:/tmp/local.txt
kubectl &lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;:/var/log/app.log ./app.log

&lt;span class=&quot;token comment&quot;&gt;# 임시 디버그 컨테이너 붙이기 (K8S 1.25+)&lt;/span&gt;
kubectl debug &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;busybox &lt;span class=&quot;token parameter variable&quot;&gt;--target&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;container&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 네임스페이스 이벤트 모아보기&lt;/span&gt;
kubectl get events --sort-by&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.lastTimestamp&apos;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ns&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;자주 만나는 에러 3종 대응표&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;Pending&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스케줄링 실패 · 리소스 부족&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl describe pod &amp;lt;name&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;CrashLoopBackOff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;앱이 시작 직후 죽음&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl logs --previous &amp;lt;name&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ImagePullBackOff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이미지 없음 · 레지스트리 권한 부족&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl describe pod &amp;lt;name&gt;&lt;/code&gt; (Events)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;패턴은 동일합니다. &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt;로 Events 보고 → 필요하면 &lt;code class=&quot;language-text&quot;&gt;logs --previous&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 컨텍스트 &amp;#x26; 네임스페이스 — 여러 클러스터 다룰 때&lt;/h2&gt;
&lt;p&gt;클러스터가 여러 개(dev·staging·prod)일 때 필수입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 현재 컨텍스트 / 전체 컨텍스트&lt;/span&gt;
kubectl config current-context
kubectl config get-contexts

&lt;span class=&quot;token comment&quot;&gt;# 컨텍스트 전환&lt;/span&gt;
kubectl config use-context prod-cluster

&lt;span class=&quot;token comment&quot;&gt;# 현재 컨텍스트의 기본 네임스페이스 변경&lt;/span&gt;
kubectl config set-context &lt;span class=&quot;token parameter variable&quot;&gt;--current&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;강력 추천: kubectx / kubens&lt;/h3&gt;
&lt;p&gt;매번 긴 명령어 치기 귀찮으면 &lt;a href=&quot;https://github.com/ahmetb/kubectx&quot;&gt;kubectx·kubens&lt;/a&gt;를 설치하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 컨텍스트 전환&lt;/span&gt;
kubectx prod

&lt;span class=&quot;token comment&quot;&gt;# 네임스페이스 전환&lt;/span&gt;
kubens production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;대화형 선택(fzf 연동)도 되고, 몇 글자 입력하면 자동완성됩니다. 실무에선 사실상 표준입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;생산성 팁 4가지&lt;/h2&gt;
&lt;h3&gt;1) alias 세팅&lt;/h3&gt;
&lt;p&gt;가장 효과 큰 한 줄입니다. 셸 rc 파일(&lt;code class=&quot;language-text&quot;&gt;~/.bashrc&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;~/.zshrc&lt;/code&gt;)에 추가하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;kubectl
&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;kg&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;kubectl get&apos;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;kd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;kubectl describe&apos;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;kl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;kubectl logs&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2) 자동완성 활성화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# bash&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kubectl completion &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;source &amp;lt;(kubectl completion bash)&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; ~/.bashrc
complete &lt;span class=&quot;token parameter variable&quot;&gt;-F&lt;/span&gt; __start_kubectl k   &lt;span class=&quot;token comment&quot;&gt;# alias에도 자동완성&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# zsh&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kubectl completion &lt;span class=&quot;token function&quot;&gt;zsh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3) &lt;code class=&quot;language-text&quot;&gt;-o jsonpath&lt;/code&gt; — 원하는 필드만 뽑기&lt;/h3&gt;
&lt;p&gt;스크립트에서 자주 쓰는 패턴입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 모든 Pod 이름&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;{.items[*].metadata.name}&apos;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Pod별 노드 확인&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;{range .items[*]}{.metadata.name}{&quot;\t&quot;}{.spec.nodeName}{&quot;\n&quot;}{end}&apos;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 이미지 목록&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;{.items[*].spec.containers[*].image}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4) 드라이런 + 서버사이드 검증&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 클라이언트 측 검증 (빠름)&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; app.yaml --dry-run&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;client

&lt;span class=&quot;token comment&quot;&gt;# 서버 측 검증 (admission webhook까지 검증됨, 더 정확)&lt;/span&gt;
kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; app.yaml --dry-run&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CI/CD에 &lt;code class=&quot;language-text&quot;&gt;--dry-run=server&lt;/code&gt;를 끼워두면 배포 사고를 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 시나리오: &quot;Pod가 안 뜹니다&quot;&lt;/h2&gt;
&lt;p&gt;입문자들이 가장 많이 겪는 상황. 순서만 정리하면 90%는 해결됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/6c707ec2cada030227dddf0cfdb32ab3/64756/diagram-debug-flow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACN0lEQVR42h2S246bMBCGeYJ2N+GQmFMwxiewCYeAMRCHTbJSG6l70/d/lnorzYzm5pv5/9E4LhBuPgVwzMhE5C2lXUD7iExQGNo9YqoPaAB4CskUUR2x2Vbbg0KBYnT8UOblRqubujyX+dX1M5SjaO9qeY3Li5TLCaucTDnTBV9sIKZzOmVYATQ4PhAt7AY2STZLqnqxlmSAWQNhj9CIrRyqKV8quVXyxoUh1UyrBdMJwIvjRvVRfOTNp4s0gHU3zKI3GW4yusnhC1WPqFhOza/G/GXDK6A67g3q7wFTPuycfXL2sU5KE5UGoJbIGZVzhrsYa1huCZ5DqE7ynonbAY4BVgFXqVxjsbhp49j0yOrRq40IdwVtCtYVvEvxCtk9588TNSG/hXwL2RbgKSiVVyqXjd6pddxT69E14FefmRj3pOypGHDVWwaVn6h8JuRq4Yh/AHoLCuXx0eNqT8ddena8rPPZeuAm4LeYXuh/mIghoyYvnzl7JMUKmLFrATUeGj02ut/wsEtqx4d9wM2x2g7WIR3KWlWNZvVoN1vNOX/ExXIk1wM1vnWHBqvZF9rWbziAA7r8ZvpPJO8pHdvByHah/FLwTfZfkN7DbAJkzc6fqXha+CB0IJeDnN8S6dgHOoo5lCsQayR0etZ42qDeoukj7+6n2tjDxvKaNhuo5h3u32n/kzQ/ivNPCwP7K3wKSx1VS/zNr0l9jeorsBO5Bmw6UnXAo19cXNTtUbfL27e83ReX96T+B2Efemp+H7pCAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/6c707ec2cada030227dddf0cfdb32ab3/a1b3b/diagram-debug-flow.webp 215w,
/static/6c707ec2cada030227dddf0cfdb32ab3/fc700/diagram-debug-flow.webp 430w,
/static/6c707ec2cada030227dddf0cfdb32ab3/521f7/diagram-debug-flow.webp 860w,
/static/6c707ec2cada030227dddf0cfdb32ab3/cbd37/diagram-debug-flow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/6c707ec2cada030227dddf0cfdb32ab3/f7886/diagram-debug-flow.png 215w,
/static/6c707ec2cada030227dddf0cfdb32ab3/bc750/diagram-debug-flow.png 430w,
/static/6c707ec2cada030227dddf0cfdb32ab3/3e3fe/diagram-debug-flow.png 860w,
/static/6c707ec2cada030227dddf0cfdb32ab3/64756/diagram-debug-flow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/6c707ec2cada030227dddf0cfdb32ab3/3e3fe/diagram-debug-flow.png&apos; alt=&apos;Pod 장애 디버깅 5단계 플로우 — get → describe → logs --previous → events → top nodes&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Pod 장애 디버깅 5단계 플로우 — get → describe → logs --previous → events → top nodes&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1. 현재 상태 확인&lt;/span&gt;
kubectl get pods

&lt;span class=&quot;token comment&quot;&gt;# 2. 상세 이벤트 확인 (가장 중요)&lt;/span&gt;
kubectl describe pod &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 3. 로그 확인 (컨테이너가 한 번이라도 떴다면)&lt;/span&gt;
kubectl logs &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;--previous&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;# 직전에 죽은 기록&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 4. 네임스페이스 전체 이벤트 스캔&lt;/span&gt;
kubectl get events --sort-by&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.lastTimestamp&apos;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ns&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 5. 노드 상태 확인&lt;/span&gt;
kubectl &lt;span class=&quot;token function&quot;&gt;top&lt;/span&gt; nodes
kubectl describe &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;node-name&lt;span class=&quot;token operator&quot;&gt;&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;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. kubectl을 매번 풀네임으로 치는 게 너무 귀찮습니다.&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;alias k=kubectl&lt;/code&gt; 한 줄이 시작입니다. 여기에 자동완성까지 붙이면 타이핑이 체감상 1/3로 줄어듭니다. &lt;code class=&quot;language-text&quot;&gt;kubectx&lt;/code&gt;·&lt;code class=&quot;language-text&quot;&gt;kubens&lt;/code&gt;까지 붙이면 사실상 완성이라고 봅니다.&lt;/p&gt;
&lt;h3&gt;Q. 명령형(&lt;code class=&quot;language-text&quot;&gt;create&lt;/code&gt;)과 선언형(&lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;) 중 뭘 써야 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;운영은 무조건 선언형(&lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt;)&lt;/strong&gt;입니다. YAML을 Git에 두고 &lt;code class=&quot;language-text&quot;&gt;kubectl apply -f&lt;/code&gt; 하나로만 배포하면 히스토리·리뷰·롤백이 모두 Git 기반으로 정리됩니다. 명령형은 일회성 실험이나 빠른 리소스 생성 시에만 사용하세요.&lt;/p&gt;
&lt;h3&gt;Q. 여러 클러스터를 자주 오갑니다. 실수로 운영에 배포할까봐 걱정입니다.&lt;/h3&gt;
&lt;p&gt;세 가지를 추천합니다. (1) &lt;code class=&quot;language-text&quot;&gt;kube-ps1&lt;/code&gt;로 셸 프롬프트에 현재 컨텍스트 표시, (2) 운영 컨텍스트는 이름을 &lt;code class=&quot;language-text&quot;&gt;🔴 prod&lt;/code&gt; 같이 경고 이모지로 시각화, (3) &lt;code class=&quot;language-text&quot;&gt;kubectl apply&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;--context=prod&lt;/code&gt; 명시적으로 추가. 눈으로 한 번 더 확인하는 장치를 만들어두는 게 안전합니다.&lt;/p&gt;
&lt;h3&gt;Q. &lt;code class=&quot;language-text&quot;&gt;kubectl exec&lt;/code&gt;가 안 먹힙니다.&lt;/h3&gt;
&lt;p&gt;두 가지 가능성이 큽니다. (1) 이미지에 shell이 없음(예: distroless) → &lt;code class=&quot;language-text&quot;&gt;kubectl debug&lt;/code&gt;로 사이드카 진입, (2) Pod가 &lt;code class=&quot;language-text&quot;&gt;Running&lt;/code&gt; 상태가 아님 → 먼저 &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt;로 상태 확인. shell 없는 프로덕션 이미지는 보안상 좋은 선택이지만, 디버깅할 땐 &lt;code class=&quot;language-text&quot;&gt;debug&lt;/code&gt; 명령이 답입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;kubectl 명령어를 한 번에 다 외울 필요는 없습니다. &lt;strong&gt;자주 쓰는 패턴부터 손에 붙이면&lt;/strong&gt; 나머지는 자연스럽게 따라옵니다. 오늘 정리한 5개 카테고리만 기억해도 충분합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조회&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;get&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;logs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배포&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;롤아웃&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;rollout status/undo/restart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디버깅&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;exec&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;port-forward&lt;/code&gt; · &lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컨텍스트&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;config use-context&lt;/code&gt; · kubectx·kubens&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음 편에서는 &lt;strong&gt;Helm으로 K8S 패키지 관리하기&lt;/strong&gt;를 다룰 예정입니다. YAML이 수백 줄로 불어나기 전에 Helm을 익혀두면, 환경별 설정 분리·버전 관리·손쉬운 롤백이 기본으로 따라옵니다.&lt;/p&gt;
&lt;h3&gt;시리즈 다음 주제&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Helm으로 K8S 패키지 관리하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD로 GitOps 배포 파이프라인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; kustomize로 환경별 YAML 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 kubectl을 쓰면서 가장 자주 헷갈리거나 &quot;이 명령어 몰랐으면 고생했겠다&quot; 싶었던 게 있나요? 댓글로 공유해주시면 다음 포스팅에 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[K8S(쿠버네티스)에 대한 이해 — Docker만으론 부족한 이유부터 핵심 개념까지]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Docker로 컨테이너 띄우는 건 익숙한데, 쿠버네티스는 자꾸 미뤄두게 됩니다." 솔직히 저도 그랬습니다.  한 줄이면 서비스가 뜨는데, 갑자기 Pod, Deployment, Service, Ingress…]]></description><link>https://blog.javapark.kr/k8s-understanding/</link><guid isPermaLink="false">https://blog.javapark.kr/k8s-understanding/</guid><pubDate>Sat, 18 Apr 2026 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Docker로 컨테이너 띄우는 건 익숙한데, 쿠버네티스는 자꾸 미뤄두게 됩니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. &lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 한 줄이면 서비스가 뜨는데, 갑자기 Pod, Deployment, Service, Ingress… 용어부터 쏟아지니 부담스럽습니다. 공식 문서를 열면 YAML이 수십 줄씩 나오고, &quot;이걸 꼭 써야 하나&quot; 싶은 순간도 많습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;K8S는 &quot;컨테이너를 운영 수준으로 다루기 위한 운영체제&quot;입니다.&lt;/strong&gt; 단일 서버에서 컨테이너 몇 개 돌릴 때는 필요 없지만, 장애 복구·스케일링·배포 자동화가 필요한 순간부터는 사실상 표준입니다. 오늘은 쿠버네티스를 처음 접하는 분들을 위해, 개념과 핵심 오브젝트를 실전 감각으로 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;K8S는 결국 뭔가? — 한 줄 정의&lt;/h2&gt;
&lt;p&gt;쿠버네티스(Kubernetes, 줄여서 K8s)는 &lt;strong&gt;컨테이너화된 애플리케이션의 배포·확장·운영을 자동화하는 오픈소스 플랫폼&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;비유하자면 이렇습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;: 물건을 담는 &lt;strong&gt;컨테이너(박스)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker Compose&lt;/strong&gt;: 한 창고 안에서 박스를 정리하는 &lt;strong&gt;정리 매뉴얼&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;: 여러 창고를 관리하며, 박스를 옮기고·복제하고·망가진 걸 다시 채워 넣는 &lt;strong&gt;자동화된 물류 시스템&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;혼자 쓰는 창고(단일 서버)라면 매뉴얼만 있어도 충분합니다. 하지만 창고가 여러 개(다중 서버)가 되고, 박스가 갑자기 쏟아져 들어오고(트래픽), 한 창고가 불이 나도 멈추면 안 되는(장애 복구) 순간부터는 K8S 같은 시스템이 필요해집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;K8S가 해결하는 문제 5가지&lt;/h2&gt;
&lt;p&gt;쿠버네티스가 왜 나왔는지 감을 잡는 가장 빠른 방법은, &lt;strong&gt;K8S 없이 운영할 때 생기는 문제&lt;/strong&gt;를 보는 겁니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제 상황&lt;/th&gt;
&lt;th&gt;K8S 없이&lt;/th&gt;
&lt;th&gt;K8S 사용 시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;트래픽이 갑자기 2배&lt;/td&gt;
&lt;td&gt;서버 수동 증설, 로드밸런서 재설정&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;replicas&lt;/code&gt; 값 변경 또는 HPA 자동 증설&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컨테이너가 갑자기 죽음&lt;/td&gt;
&lt;td&gt;모니터링 → 수동 재시작&lt;/td&gt;
&lt;td&gt;자동 재시작 + 자동 스케줄링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;무중단 배포&lt;/td&gt;
&lt;td&gt;Blue-Green 직접 구성, 스크립트 관리&lt;/td&gt;
&lt;td&gt;RollingUpdate 전략 기본 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 한 대가 장애&lt;/td&gt;
&lt;td&gt;해당 서버 위 컨테이너 전부 사라짐&lt;/td&gt;
&lt;td&gt;다른 노드로 자동 재배치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;환경변수·설정 관리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;.env&lt;/code&gt; 파일 복사·보안 관리 수동&lt;/td&gt;
&lt;td&gt;ConfigMap / Secret으로 표준화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심 키워드는 &lt;strong&gt;자가 치유(self-healing)&lt;/strong&gt;와 &lt;strong&gt;선언적 운영(declarative)&lt;/strong&gt;입니다. &quot;이런 상태를 원한다&quot;고 YAML로 선언하면, K8S가 알아서 그 상태에 맞춰줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;핵심 컴포넌트 — Control Plane과 Worker Node&lt;/h2&gt;
&lt;p&gt;K8S 클러스터는 크게 &lt;strong&gt;Control Plane(두뇌)&lt;/strong&gt;과 &lt;strong&gt;Worker Node(실행 영역)&lt;/strong&gt; 두 영역으로 나뉩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/0d7c192af631ecac24ee87f74fc89edf/64756/diagram-architecture.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACD0lEQVR42l2S226bQBCGeYMGA2aXPdiY4xpzPi0Hm2C7SV0rkXJRqer7P0jHROlFpe9itMw3/2hZ5RtNVK9X/YEfjm46eemzHR2x32MxsWii0TOJZipG6kseDlwMLBxY0FFPWp5UVJ6qybzKz257jftbOt6D8sKyK65+bpo7r2+kfaPphYqORQMVPQk7Ekjst9htlCcW66kkw6ylvRG2xl7S9ETiAYkGiZYlo51Pm3xi2YkWk1VOOD8CKO5NpwI54ekp6m70MJJ9Tw5DPN6d4sziEeqgfSnPH1F/g1jSXaz5lQwXNMxmeVzbpbLi2WqbanZmBI3h1Wu3XLsVDhoL2PfWfrDCloQSwEGNhUSiM93CcIr1tlB0nutOq3tS97q116FgxOFoiaMVzySG/JlCEU1wCFdoBr0JbY403M7YgLzJWfO263/z7Ir2Ey7uKLnYzYf//IeXP2hytqq7mb2y/Ltz+rUt300x6fFFS140liuQjpwau3CB3YK0fMnCke9HImDnAU6Q1yK3AUxXGk6r2dVqU65Yppi7Gu1q7NSW2xCvpUHHRQ/QAJAEZrktfEK7am1XxrYE9G2pPeRcQf80Xz7MsH+8BDFATf3O8uB/tjDaXOTFr7TtZ3KuYKeBDrKY7H9TPjKdBoG2ZH6ZlcpLlWYKdHxlfr4+2Lb73BY/tl0yQdsU2sKKF0+sgBEqy/8CzvR0vfoNHCEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/0d7c192af631ecac24ee87f74fc89edf/a1b3b/diagram-architecture.webp 215w,
/static/0d7c192af631ecac24ee87f74fc89edf/fc700/diagram-architecture.webp 430w,
/static/0d7c192af631ecac24ee87f74fc89edf/521f7/diagram-architecture.webp 860w,
/static/0d7c192af631ecac24ee87f74fc89edf/cbd37/diagram-architecture.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/0d7c192af631ecac24ee87f74fc89edf/f7886/diagram-architecture.png 215w,
/static/0d7c192af631ecac24ee87f74fc89edf/bc750/diagram-architecture.png 430w,
/static/0d7c192af631ecac24ee87f74fc89edf/3e3fe/diagram-architecture.png 860w,
/static/0d7c192af631ecac24ee87f74fc89edf/64756/diagram-architecture.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/0d7c192af631ecac24ee87f74fc89edf/3e3fe/diagram-architecture.png&apos; alt=&apos;K8S 클러스터 구조 — Control Plane(API Server·Scheduler·Controller Manager·etcd)과 Worker Node 3대&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;K8S 클러스터 구조 — Control Plane(API Server·Scheduler·Controller Manager·etcd)과 Worker Node 3대&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;역할만 간단히 정리하면:&lt;/p&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;&lt;strong&gt;API Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;모든 요청의 창구 (&lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt;이 말 거는 대상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;etcd&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;클러스터의 &quot;진실의 원천&quot; — 모든 상태 저장소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scheduler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;새 Pod를 어느 노드에 띄울지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Controller Manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실제 상태와 원하는 상태의 차이를 계속 맞춤&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kubelet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;각 노드의 에이전트 — 컨테이너를 실제로 띄우고 보고함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;처음엔 외우지 말고, &quot;API Server가 입구, etcd가 저장소&quot; 정도만 기억해도 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;꼭 알아야 할 오브젝트 5가지&lt;/h2&gt;
&lt;p&gt;K8S는 모든 것이 오브젝트(Object)입니다. 그중 입문자가 반드시 익혀야 할 5가지만 뽑았습니다.&lt;/p&gt;
&lt;h3&gt;1. Pod — 가장 작은 배포 단위&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 1개 이상을 묶은 실행 단위&lt;/li&gt;
&lt;li&gt;같은 Pod 안의 컨테이너는 네트워크·스토리지를 공유&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pod는 언제든 죽을 수 있다&lt;/strong&gt;는 전제로 설계됨 (그래서 단독으로 만드는 일은 드뭅니다)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Deployment — Pod의 &quot;관리자&quot;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pod를 몇 개 띄울지, 어떻게 업데이트할지 선언&lt;/li&gt;
&lt;li&gt;롤링 업데이트·롤백 기본 지원&lt;/li&gt;
&lt;li&gt;실무에선 &lt;strong&gt;Pod를 직접 만들지 않고 Deployment로 만듭니다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Service — 접근 경로&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pod는 죽으면 IP가 바뀝니다. Service가 고정된 엔드포인트를 제공&lt;/li&gt;
&lt;li&gt;타입별 차이:&lt;/li&gt;
&lt;/ul&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;&lt;code class=&quot;language-text&quot;&gt;ClusterIP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클러스터 내부 전용 (기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;NodePort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;노드 IP:포트로 외부 노출 (개발용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;LoadBalancer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클라우드 LB 자동 생성 (운영용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;4. ConfigMap / Secret — 설정 분리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ConfigMap&lt;/strong&gt;: 일반 설정 (환경변수, 설정 파일)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret&lt;/strong&gt;: 민감 정보 (API 키, 비밀번호) — Base64 인코딩 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드와 설정을 분리해야 같은 이미지를 dev·staging·prod에 재사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;5. Ingress — HTTP 라우팅의 진입점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;도메인·경로 기반 라우팅 (&lt;code class=&quot;language-text&quot;&gt;api.example.com&lt;/code&gt; → api Service, &lt;code class=&quot;language-text&quot;&gt;/admin&lt;/code&gt; → admin Service)&lt;/li&gt;
&lt;li&gt;TLS 종단 처리&lt;/li&gt;
&lt;li&gt;Nginx·Traefik 같은 Ingress Controller가 실제 트래픽 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 오브젝트들이 실제 요청을 처리하는 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/c382773fa796f1e39da1e76bcf9286bc/64756/diagram-objects-flow.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB90lEQVR42m2S64rbMBCF/QbdTWIrji3J9/tFkiXf4sS5sN2W/tm2UGjp+z9HZ50UQikchBj8zRnrjPYB10/RiNKRFpNXzX45x+xCqisKh60nzaDbhZ0V9XY84GQk6R6D4sGOBqhrT5Ststkuj6m6Fv1rrl7q/pOnvmyTaecrK+zshSQ3MtnDHXoBaQat9kzqFa1XDnuGLpRtKFsTjhxu+upfzzsJnv0u6La+0takXgPgNronDU/prkJ+Cxh88ei5wItn0EFf+CPkSk2nPGBTVvGqrhnjjeBwRrmIC5lWbV51edWmZZsUKimh0uVsyOseKqbbaDBnkPK0FEnRhBkLszbJD3F5DPODn49edXDZKeKnkM00aWnRedWAU2nH0nC4hiiTgh3ni+rPcc5INNXyVzv9rNQ3N7li+Zmef2fnHyA9Gu3u7I4fUTUZ5QhPo20dVvKByUmofcFbGo2lfCubN95997OLI17S6SsWrxZ7MaKOiBORF0ddMJ/XmGnIbXYet/zG8oUVSivsSTx4+Z7EE04nK96bybCLe8OV73Ia3RFrwoDc2ODsKfP9eXvIwI7HWyo0ewwGIu22rkSORFQtahFWG1toC/m/TQjvmwDdYTrwNBxpUImINLDSsVxbQnvI8xbmeN+h4C/pLdNSoROh4+YmRNTGEn8ATz12UoBQMVkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/c382773fa796f1e39da1e76bcf9286bc/a1b3b/diagram-objects-flow.webp 215w,
/static/c382773fa796f1e39da1e76bcf9286bc/fc700/diagram-objects-flow.webp 430w,
/static/c382773fa796f1e39da1e76bcf9286bc/521f7/diagram-objects-flow.webp 860w,
/static/c382773fa796f1e39da1e76bcf9286bc/cbd37/diagram-objects-flow.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/c382773fa796f1e39da1e76bcf9286bc/f7886/diagram-objects-flow.png 215w,
/static/c382773fa796f1e39da1e76bcf9286bc/bc750/diagram-objects-flow.png 430w,
/static/c382773fa796f1e39da1e76bcf9286bc/3e3fe/diagram-objects-flow.png 860w,
/static/c382773fa796f1e39da1e76bcf9286bc/64756/diagram-objects-flow.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/c382773fa796f1e39da1e76bcf9286bc/3e3fe/diagram-objects-flow.png&apos; alt=&apos;K8S 오브젝트 흐름도 — 외부 요청이 Ingress → Service → Deployment → Pod로 전달되는 과정&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;K8S 오브젝트 흐름도 — 외부 요청이 Ingress → Service → Deployment → Pod로 전달되는 과정&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시 — nginx를 K8S에 띄우기&lt;/h2&gt;
&lt;p&gt;이론만으론 감이 안 옵니다. nginx 하나를 Deployment + Service로 띄우는 최소 YAML을 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;deploy
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
    &lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
          &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.27&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Service
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;svc
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ClusterIP
  &lt;span class=&quot;token key atrule&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx
  &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;적용과 확인은 세 줄로 끝납니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; nginx.yaml
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx
kubectl port-forward svc/nginx-svc &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;:80&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;http://localhost:8080&lt;/code&gt;으로 접속하면 nginx가 응답합니다. Pod 하나를 강제로 삭제해봐도 곧바로 새 Pod가 올라오는 걸 확인할 수 있습니다 — 이게 &lt;strong&gt;자가 치유&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Pod 하나 강제 삭제&lt;/span&gt;
kubectl delete pod &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pod-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 몇 초 뒤 확인해보면 새 Pod가 자동 생성됨&lt;/span&gt;
kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;전체 자가 치유 과정을 시각화하면 이렇습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/98fefe05867293d51342d979addfeef3/64756/diagram-self-healing.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACPklEQVR42h3QWY6cMBQFUFaQroG5zDzbTAYbMBhDQVUpHamVREr+sv91hG7p6P1dXb0rnYzi4g/XgNkxi/LFR3NcrbDZQ8QBZKC8W9lsRQNIRiudnGyy0xEkA4gGM+ql8608QXGBIm422L2K9tHSJ2XfcbM5+ejRh4dGL6Y+ZE42mlCYGTfTyYhHI+ykEygcuhpwOIXNW9gAp2bNFvhNGpDQaynun/s7pgJVvZvRWPzxybuO1oMWdEdzYfJdmzeZznIzAb8pxSujS0ZElPYJ4e3PD7RuySSchNj8w9p/a/272j5Un0gXUF74Im+7zO9H3vJw385dI0jFYTKEmCX7FnORTLMTtwr7pb/+qfNfhf5Q3Va62pVMuNIvKhUK5lZAom6JWpG0S5iPMexgOaRFl+bUDluVPDT2Utpdqe/KEZZdrCSdlg06ZBpkdkDSeEgTBrMxOppRl5Z9nNMEERA1aiq+zErMZRtLqteosNcQ09Go55MT0ixlaTZCOIVJH0ByJEPURqgBEZYzcbgm/BqNV7uWVL/V8xFUwqoXs+Ru3OX5jPK5KI7BhgBSWA8Qswz3txgrcFHR+nmz+QIqSQuIg9ewexxu1eynDNcbhLwqlyDuvYzU/Ur4s+xmI6yNcrPx61ZsoNhOt1LSw05Dg5oPWj4YxeS0a8qfLrt74z1gu9sIB0/WF71g55SeE/IW4G9udTYLyYyHGxwBmqxiPtilcOrl+AKUMyi4CUcDjjr63FJOukvYXoLm7GLZby638j9fx30j9wU63gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/98fefe05867293d51342d979addfeef3/a1b3b/diagram-self-healing.webp 215w,
/static/98fefe05867293d51342d979addfeef3/fc700/diagram-self-healing.webp 430w,
/static/98fefe05867293d51342d979addfeef3/521f7/diagram-self-healing.webp 860w,
/static/98fefe05867293d51342d979addfeef3/cbd37/diagram-self-healing.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/98fefe05867293d51342d979addfeef3/f7886/diagram-self-healing.png 215w,
/static/98fefe05867293d51342d979addfeef3/bc750/diagram-self-healing.png 430w,
/static/98fefe05867293d51342d979addfeef3/3e3fe/diagram-self-healing.png 860w,
/static/98fefe05867293d51342d979addfeef3/64756/diagram-self-healing.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/98fefe05867293d51342d979addfeef3/3e3fe/diagram-self-healing.png&apos; alt=&apos;K8S 자가 치유 플로우 — Pod 장애 감지부터 자동 복구까지 4단계&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;K8S 자가 치유 플로우 — Pod 장애 감지부터 자동 복구까지 4단계&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;운영자 개입 없이 컨트롤러가 &lt;strong&gt;원하는 상태(desired state)&lt;/strong&gt;를 기준으로 차이를 감지하고 복구합니다. 이 선언적 운영 모델이 K8S의 가장 강력한 장점입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;K8S를 도입해야 할 때 vs 안 해도 될 때&lt;/h2&gt;
&lt;p&gt;솔직히 말씀드리면, &lt;strong&gt;모든 프로젝트에 K8S가 필요한 건 아닙니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;도입을 추천하는 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서비스 수가 10개 이상이거나 앞으로 늘어날 계획&lt;/li&gt;
&lt;li&gt;트래픽 변동폭이 커서 오토스케일링이 필요&lt;/li&gt;
&lt;li&gt;무중단 배포·카나리 배포가 요구사항&lt;/li&gt;
&lt;li&gt;여러 환경(dev/staging/prod)을 일관되게 운영해야 함&lt;/li&gt;
&lt;li&gt;팀에 인프라/DevOps 담당자가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;도입을 재고해야 하는 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단일 서비스, 트래픽 안정적, 작은 팀&lt;/li&gt;
&lt;li&gt;운영 인력 없이 개발자 1~2명&lt;/li&gt;
&lt;li&gt;Docker Compose나 관리형 PaaS(Cloud Run, ECS Fargate 등)로 충분한 규모&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;K8S는 &lt;strong&gt;강력한 만큼 복잡합니다.&lt;/strong&gt; 러닝 커브와 운영 비용을 지불할 준비가 됐을 때 도입하는 게 맞습니다. 처음부터 &quot;일단 K8S&quot;는 오히려 생산성을 떨어뜨리는 경우가 많습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Docker Swarm과 K8S 중 뭘 써야 하나요?&lt;/h3&gt;
&lt;p&gt;사실상 K8S가 표준입니다. Docker Swarm은 설정이 간단한 대신 생태계가 얇고, 클라우드 3사(AWS·GCP·Azure)가 모두 관리형 K8S(EKS·GKE·AKS)를 제공합니다. 학습 리소스, 채용 시장, 도구 생태계 모두 K8S 중심으로 움직입니다.&lt;/p&gt;
&lt;h3&gt;Q. 공부는 로컬에서 어떻게 시작하나요?&lt;/h3&gt;
&lt;p&gt;가볍게 시작할 수 있는 옵션이 많습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docker Desktop의 Kubernetes&lt;/strong&gt;: 클릭 한 번으로 활성화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;minikube&lt;/strong&gt;: 단일 노드 로컬 클러스터&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kind&lt;/strong&gt;: Docker 컨테이너로 K8S 노드를 띄우는 방식, CI에도 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실습은 &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 명령어 익숙해지는 것부터 시작하시면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q. 직접 구축 vs 관리형 서비스, 뭘 써야 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;처음엔 무조건 관리형(EKS/GKE/AKS)입니다.&lt;/strong&gt; 직접 구축(self-hosted)은 Control Plane 운영·업그레이드·백업까지 직접 책임져야 하고, 보안 이슈도 많습니다. 비용이 문제라면 단일 노드 K3s 같은 경량 대안도 있지만, 이건 규모를 충분히 이해한 다음 선택하는 게 맞습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리 — 다음 단계&lt;/h2&gt;
&lt;p&gt;오늘 정리한 내용은 K8S 전체의 입구일 뿐입니다. 핵심만 다시 짚으면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;K8S는 &lt;strong&gt;컨테이너 운영을 자동화하는 플랫폼&lt;/strong&gt;, Docker의 상위 개념이 아니라 &lt;strong&gt;운영 레이어&lt;/strong&gt;입니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pod → Deployment → Service → Ingress&lt;/strong&gt; 흐름이 가장 기본&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;선언적 YAML&lt;/strong&gt;과 &lt;strong&gt;자가 치유&lt;/strong&gt;가 K8S를 쓰는 진짜 이유&lt;/li&gt;
&lt;li&gt;작은 프로젝트엔 과합니다 — 규모와 팀 상황에 맞게 판단하세요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;앞으로는 이 시리즈로 다음 주제들을 이어갈 예정입니다.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;code class=&quot;language-text&quot;&gt;kubectl&lt;/code&gt; 실전 명령어 모음&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Helm으로 패키지 관리하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HPA·VPA로 오토스케일링 구성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ingress + cert-manager로 HTTPS 자동화&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; ArgoCD로 GitOps 배포 파이프라인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러분은 K8S를 처음 접했을 때 어떤 부분이 가장 헷갈리셨나요? 댓글로 공유해주시면 다음 포스팅 주제에 반영하겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Agentic AI 2026 실전 가이드 — 시장 현황부터 프레임워크 선택, 도입 전략까지]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Agentic AI가 뭔지는 알겠는데, 실제로 우리 조직에 도입하려면 어디서부터 시작해야 하죠?" 지난 포스팅에서 Agentic AI의 개념과 기존 생성형 AI와의 차이를 다뤘는데요, 이번에는 202…]]></description><link>https://blog.javapark.kr/agentic-ai-2026-enterprise-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/agentic-ai-2026-enterprise-guide/</guid><pubDate>Thu, 16 Apr 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;figure class=&apos;gatsby-resp-image-figure&apos; style=&apos;margin-bottom: 16px;&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-wrapper&apos; style=&apos;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 860px; &apos;&gt;
      &lt;a class=&apos;gatsby-resp-image-link&apos; href=&apos;/static/babe7e451de944b81de8cb3356a2f9ee/64756/thumbnail.png&apos; style=&apos;display: block&apos; target=&apos;_blank&apos; rel=&apos;noopener&apos;&gt;
    &lt;span class=&apos;gatsby-resp-image-background-image&apos; style=&quot;padding-bottom: 52.55813953488372%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABp0lEQVR42oWSQW/TQBCFfalyaT2OY3u9u07j2LGzTu06sZN1Y6dRUQ51gYpeEFJQK1RuILWN1N5aQAgOXPgD8GNZKwGaChVptNKu9O17M28k0APQGRjBI6WiSMcpohmiXMNJHYXLdwnMEEgMOFRQD4zeP+GGGRuUU6sgVq6ToWruruC6M274UxWHGgoUvQuijIdGVDMSgkLWtDKdJupKKZBqz282nl7XDi82ykVt8gZefQKbg+bfc8GIn4e8ZP0ZcUbE5cQdmU5awSdXX+Zffx69/3h69/3k3d1W/Axo//4UFKOLWM54mRbH7d6Eehx3ljCTzucfLs6/vZ1/vl38ePniclNuKQ9tM+qMe8lhPz9qdveIx8kfuBheTkeLJDybZjdR+FoWzaC1sYm/sJf5g9nuXulGU8vP/sKaWzTsFOyhvB3LzQFYCeBoDdaZ4aV2NPH7B60gx24qSNROKhiz/WY0M+NS6RTgjKGdQTOp8lsXlzV/S/NkkQVi4rpsTaqCEVLbQ+hMoHtQnS1eJf/o2vxeErQD1gDsDLx9YE9WMP0PXDd3RNS/AO7/XmKmHBoZAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&apos;/static/babe7e451de944b81de8cb3356a2f9ee/a1b3b/thumbnail.webp 215w,
/static/babe7e451de944b81de8cb3356a2f9ee/fc700/thumbnail.webp 430w,
/static/babe7e451de944b81de8cb3356a2f9ee/521f7/thumbnail.webp 860w,
/static/babe7e451de944b81de8cb3356a2f9ee/cbd37/thumbnail.webp 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/webp&apos;&gt;
          &lt;source srcset=&apos;/static/babe7e451de944b81de8cb3356a2f9ee/f7886/thumbnail.png 215w,
/static/babe7e451de944b81de8cb3356a2f9ee/bc750/thumbnail.png 430w,
/static/babe7e451de944b81de8cb3356a2f9ee/3e3fe/thumbnail.png 860w,
/static/babe7e451de944b81de8cb3356a2f9ee/64756/thumbnail.png 1200w&apos; sizes=&apos;(max-width: 860px) 100vw, 860px&apos; type=&apos;image/png&apos;&gt;
          &lt;img class=&apos;gatsby-resp-image-image&apos; src=&apos;/static/babe7e451de944b81de8cb3356a2f9ee/3e3fe/thumbnail.png&apos; alt=&apos;Agentic AI 2026 엔터프라이즈 도입 가이드 커버 — 프레임워크 비교와 4단계 로드맵&apos; title=&apos;&apos; loading=&apos;lazy&apos; decoding=&apos;async&apos; style=&apos;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&apos;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&apos;gatsby-resp-image-figcaption&apos;&gt;Agentic AI 2026 엔터프라이즈 도입 가이드 커버 — 프레임워크 비교와 4단계 로드맵&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Agentic AI가 뭔지는 알겠는데, 실제로 우리 조직에 도입하려면 어디서부터 시작해야 하죠?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/agentic-ai/&quot;&gt;지난 포스팅&lt;/a&gt;에서 Agentic AI의 개념과 기존 생성형 AI와의 차이를 다뤘는데요, 이번에는 &lt;strong&gt;2026년 현재 시장이 어디까지 왔는지&lt;/strong&gt;, 그리고 &lt;strong&gt;실전에서 어떤 프레임워크를 선택하고, 어떻게 도입해야 하는지&lt;/strong&gt;를 정리해봤습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — 2026년은 Agentic AI가 &lt;strong&gt;파일럿에서 프로덕션으로 넘어가는 원년&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;📊 2026년 Agentic AI 시장, 숫자로 보기&lt;/h2&gt;
&lt;p&gt;직접 조사해보니, 주요 리서치 기관들의 수치가 한 방향을 가리키고 있습니다.&lt;/p&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;엔터프라이즈 앱 내 AI 에이전트 탑재율&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40%&lt;/strong&gt; (2025년 5% 미만에서 급증)&lt;/td&gt;
&lt;td&gt;Gartner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic AI 시장 규모&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;78억 달러&lt;/strong&gt; → 2030년 520억 달러 전망&lt;/td&gt;
&lt;td&gt;업계 종합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 에이전트 구축 중인 조직 비율&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Omdena&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 에이전트 스케일링이 경쟁 우위라고 답한 리더&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;업계 서베이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;산업별 최고 도입률 (통신)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OneReach&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;솔직히 이 정도면 &quot;도입할까 말까&quot;가 아니라 &lt;strong&gt;&quot;어떻게 도입할까&quot;&lt;/strong&gt; 단계입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🔥 2026년 Agentic AI 5대 트렌드&lt;/h2&gt;
&lt;h3&gt;1. 파일럿에서 프로덕션으로&lt;/h3&gt;
&lt;p&gt;Gartner, Forrester, IDC 등 주요 분석 기관의 메시지가 일치합니다. &lt;strong&gt;2026년은 에이전트를 실험 단계에서 운영 환경으로 옮기는 해&lt;/strong&gt;입니다. 더 이상 PoC만으로는 경쟁에서 살아남기 어렵습니다.&lt;/p&gt;
&lt;h3&gt;2. 멀티 에이전트 오케스트레이션&lt;/h3&gt;
&lt;p&gt;단일 에이전트가 아닌, &lt;strong&gt;전문화된 여러 에이전트가 협업하는 아키텍처&lt;/strong&gt;가 주류로 자리 잡고 있습니다. 마치 회사에서 기획자, 개발자, QA가 각자 역할을 나눠 일하듯, AI 에이전트들도 역할 분담과 협업을 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[사용자 요청]
    ↓
[오케스트레이터 에이전트]
    ├── [리서치 에이전트] → 정보 수집
    ├── [분석 에이전트] → 데이터 분석
    └── [실행 에이전트] → 결과물 생성&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. 거버넌스가 핵심 설계 요소로&lt;/h3&gt;
&lt;p&gt;2025년까지는 거버넌스를 &quot;규제 때문에 어쩔 수 없이&quot; 하는 분위기였다면, 2026년에는 &lt;strong&gt;거버넌스가 잘 갖춰진 조직이 오히려 더 과감하게 에이전트를 배포&lt;/strong&gt;하는 패턴이 나타나고 있습니다. 신뢰가 확보되니 활용 범위가 넓어지는 거죠.&lt;/p&gt;
&lt;h3&gt;4. 노코드/로우코드 민주화&lt;/h3&gt;
&lt;p&gt;에이전트 개발이 더 이상 전문 엔지니어팀의 전유물이 아닙니다. &lt;strong&gt;비즈니스 사용자가 직접 에이전트를 설계하고 배포&lt;/strong&gt;하는 플랫폼들이 빠르게 성장하고 있습니다.&lt;/p&gt;
&lt;h3&gt;5. 비용 최적화가 설계 단계부터&lt;/h3&gt;
&lt;p&gt;에이전트를 만들고 나서 비용을 줄이는 게 아니라, &lt;strong&gt;설계 단계에서부터 경제 모델을 함께 설계&lt;/strong&gt;하는 것이 2026년의 핵심 트렌드입니다. 토큰 사용량, API 호출 횟수, 실행 시간 등을 아키텍처 레벨에서 관리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🛠️ Agentic AI 주요 프레임워크 비교 (2026)&lt;/h2&gt;
&lt;p&gt;프레임워크 선택이 곧 프로젝트의 성패를 좌우합니다. 2026년 현재 가장 많이 쓰이는 프레임워크들을 정리했습니다.&lt;/p&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;th&gt;난이도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CrewAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;역할 기반 멀티 에이전트, 직관적 API&lt;/td&gt;
&lt;td&gt;팀 기반 워크플로우 자동화&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;그래프 기반 상태 머신, LangChain 생태계&lt;/td&gt;
&lt;td&gt;복잡한 분기/반복 워크플로우&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AutoGen&lt;/strong&gt; (Microsoft)&lt;/td&gt;
&lt;td&gt;대화 기반 멀티 에이전트, 코드 실행&lt;/td&gt;
&lt;td&gt;연구, 코드 생성, 분석&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Agent SDK&lt;/strong&gt; (Anthropic)&lt;/td&gt;
&lt;td&gt;가드레일 내장, 핸드오프 패턴&lt;/td&gt;
&lt;td&gt;안전성 중시 프로덕션 앱&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semantic Kernel&lt;/strong&gt; (Microsoft)&lt;/td&gt;
&lt;td&gt;.NET/Java/Python 지원, 기업 통합&lt;/td&gt;
&lt;td&gt;기존 엔터프라이즈 시스템 연동&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaIndex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RAG 특화, 데이터 커넥터 풍부&lt;/td&gt;
&lt;td&gt;문서 기반 에이전트&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;프레임워크 선택 가이드&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;빠르게 프로토타입&lt;/strong&gt;하고 싶다면 → &lt;strong&gt;CrewAI&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;역할(Role), 목표(Goal), 백스토리(Backstory)만 정의하면 바로 멀티 에이전트 시스템 구축 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; crewai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Agent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Crew

researcher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Agent&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    role&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;시장 조사 전문가&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    goal&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Agentic AI 시장 동향 분석&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    backstory&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10년 경력의 IT 시장 분석가&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;search_tool&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; web_scraper&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Agent&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    role&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;기술 콘텐츠 작가&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    goal&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;조사 결과를 블로그 포스팅으로 작성&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    backstory&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;개발자 출신 테크 블로거&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

crew &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Crew&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    agents&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;researcher&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    tasks&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;research_task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; writing_task&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    verbose&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crew&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kickoff&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;복잡한 워크플로우&lt;/strong&gt;를 세밀하게 제어하고 싶다면 → &lt;strong&gt;LangGraph&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상태 그래프로 에이전트의 실행 흐름을 명시적으로 설계 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;프로덕션 안전성&lt;/strong&gt;이 최우선이라면 → &lt;strong&gt;Claude Agent SDK&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가드레일, 핸드오프, 입출력 검증이 프레임워크 레벨에서 내장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;🗺️ 실전 도입 로드맵 — 4단계 접근법&lt;/h2&gt;
&lt;p&gt;Agentic AI를 우리 조직에 도입한다면, 저는 이 순서를 추천합니다.&lt;/p&gt;
&lt;h3&gt;Phase 1: 단일 에이전트로 시작 (1~2주)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가장 반복적이고 시간이 많이 드는 업무 1개를 선정&lt;/li&gt;
&lt;li&gt;단일 에이전트 + 2~3개 도구로 자동화&lt;/li&gt;
&lt;li&gt;예시: 일일 리포트 생성, 코드 리뷰 자동화, 고객 문의 분류&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Phase 2: 도구 확장 및 평가 체계 구축 (2~4주)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;에이전트가 사용할 수 있는 도구(API, DB, 파일 시스템 등) 확장&lt;/li&gt;
&lt;li&gt;성공 기준 정의 및 자동 평가 파이프라인 구축&lt;/li&gt;
&lt;li&gt;비용 모니터링 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Phase 3: 멀티 에이전트 아키텍처 (1~2개월)&lt;/h3&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;h3&gt;Phase 4: 거버넌스 및 스케일링 (지속)&lt;/h3&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;hr&gt;
&lt;h2&gt;⚠️ 도입 시 주의할 3가지&lt;/h2&gt;
&lt;h3&gt;1. &quot;만능 에이전트&quot; 함정을 피하세요&lt;/h3&gt;
&lt;p&gt;하나의 에이전트에 모든 기능을 넣으려는 유혹은 강합니다. 하지만 실전에서는 &lt;strong&gt;역할이 명확한 작은 에이전트 여러 개&lt;/strong&gt;가 훨씬 안정적입니다.&lt;/p&gt;
&lt;h3&gt;2. Human-in-the-loop을 설계하세요&lt;/h3&gt;
&lt;p&gt;완전 자율이 아니라, &lt;strong&gt;중요한 의사결정 지점에 사람이 확인하는 단계&lt;/strong&gt;를 넣어야 합니다. 특히 금융, 의료, 법률 분야에서는 필수입니다.&lt;/p&gt;
&lt;h3&gt;3. 비용을 과소평가하지 마세요&lt;/h3&gt;
&lt;p&gt;에이전트는 반복적으로 LLM을 호출합니다. &lt;strong&gt;멀티 에이전트 시스템에서 토큰 사용량은 기하급수적으로 늘어날 수 있습니다.&lt;/strong&gt; 캐싱, 작은 모델 혼용, 호출 최소화 전략이 반드시 필요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🏭 산업별 Agentic AI 도입 현황&lt;/h2&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;통신&lt;/td&gt;
&lt;td&gt;48%&lt;/td&gt;
&lt;td&gt;네트워크 모니터링, 고객 서비스 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리테일/CPG&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;td&gt;재고 관리, 개인화 추천, 가격 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;헬스케어&lt;/td&gt;
&lt;td&gt;급성장&lt;/td&gt;
&lt;td&gt;임상 문서 자동화, 약물 상호작용 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;금융&lt;/td&gt;
&lt;td&gt;확대 중&lt;/td&gt;
&lt;td&gt;사기 탐지, 리스크 분석, 규제 준수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제조&lt;/td&gt;
&lt;td&gt;도입 초기&lt;/td&gt;
&lt;td&gt;품질 검사 자동화, 공급망 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;❓ FAQ — 자주 묻는 질문&lt;/h2&gt;
&lt;h3&gt;Q. Agentic AI와 기존 RPA(로봇 프로세스 자동화)는 뭐가 다른가요?&lt;/h3&gt;
&lt;p&gt;RPA는 &lt;strong&gt;정해진 규칙에 따라 반복 작업을 수행&lt;/strong&gt;하는 자동화입니다. 예외 상황이 발생하면 멈춥니다. 반면 Agentic AI는 &lt;strong&gt;상황을 이해하고 스스로 판단하여 대응&lt;/strong&gt;합니다. 비유하자면 RPA는 &quot;매뉴얼대로 일하는 직원&quot;, Agentic AI는 &quot;상황 판단이 가능한 경력 직원&quot;입니다.&lt;/p&gt;
&lt;h3&gt;Q. Agentic AI 도입에 필요한 최소 기술 스택은?&lt;/h3&gt;
&lt;p&gt;LLM API(OpenAI, Claude 등) + 에이전트 프레임워크(CrewAI, LangGraph 등) + 벡터 DB(선택) + 모니터링 도구 정도면 시작할 수 있습니다. 클라우드 인프라는 기존에 쓰고 있는 것을 활용하면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q. 소규모 팀도 Agentic AI를 도입할 수 있나요?&lt;/h3&gt;
&lt;p&gt;물론입니다. 오히려 &lt;strong&gt;소규모 팀이 Agentic AI의 가장 큰 수혜자&lt;/strong&gt;입니다. 사람이 부족한 영역을 에이전트가 보완해주기 때문입니다. CrewAI나 Claude Agent SDK처럼 진입 장벽이 낮은 프레임워크부터 시작해보세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;2026년, Agentic AI는 더 이상 미래 기술이 아닙니다. Gartner가 예측한 대로 엔터프라이즈 앱의 40%가 AI 에이전트를 탑재하는 시대가 이미 시작됐습니다.&lt;/p&gt;
&lt;p&gt;중요한 것은 &lt;strong&gt;완벽한 시스템을 처음부터 만들려 하지 않는 것&lt;/strong&gt;입니다. 작은 단일 에이전트로 시작해서, 검증하고, 점진적으로 확장하세요.&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 실제로 CrewAI를 활용해 멀티 에이전트 시스템을 구축하는 실습을 다뤄볼 예정입니다.&lt;/p&gt;
&lt;p&gt;여러분은 Agentic AI, 어디까지 경험해보셨나요? 댓글로 공유해주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고 자료:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gartner.com/en/newsroom/press-releases/2025-08-26-gartner-predicts-40-percent-of-enterprise-apps-will-feature-task-specific-ai-agents-by-2026-up-from-less-than-5-percent-in-2025&quot;&gt;Gartner: 2026년까지 엔터프라이즈 앱 40%가 AI 에이전트 탑재 전망&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://onereach.ai/blog/agentic-ai-adoption-rates-roi-market-trends/&quot;&gt;Agentic AI Stats 2026: Adoption Rates, ROI, &amp;#x26; Market Trends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://machinelearningmastery.com/7-agentic-ai-trends-to-watch-in-2026/&quot;&gt;7 Agentic AI Trends to Watch in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mitsloan.mit.edu/ideas-made-to-matter/agentic-ai-explained&quot;&gt;Agentic AI, explained — MIT Sloan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.instaclustr.com/education/agentic-ai/agentic-ai-frameworks-top-10-options-in-2026/&quot;&gt;Agentic AI Frameworks: Top 10 Options in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ibm.com/think/news/ai-tech-trends-predictions-2026&quot;&gt;IBM: AI &amp;#x26; Tech Trends 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Agentic AI (에이전틱 AI)란 무엇인가? — 생성형 AI를 넘어 '행동하는 AI'의 시대로]]></title><description><![CDATA[Agentic AI(에이전틱 AI) 개념 커버 이미지 — 행동하는 AI의 4가지 핵심 요소 안녕하세요, 자바파커입니다. 최근 AI 기술의 흐름이 단순한 텍스트나 이미지 생성을 넘어, 스스로 생각하고 직접 행동하는 Agentic AI(에이전틱 AI…]]></description><link>https://blog.javapark.kr/agentic-ai/</link><guid isPermaLink="false">https://blog.javapark.kr/agentic-ai/</guid><pubDate>Thu, 16 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/agentic-ai-guide.png&quot; alt=&quot;Agentic AI(에이전틱 AI) 개념 커버 이미지 — 행동하는 AI의 4가지 핵심 요소&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;최근 AI 기술의 흐름이 단순한 텍스트나 이미지 생성을 넘어, 스스로 생각하고 직접 행동하는 &lt;strong&gt;Agentic AI(에이전틱 AI)&lt;/strong&gt; 로 빠르게 진화하고 있습니다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서는 Agentic AI가 도대체 무엇인지, 우리가 흔히 아는 챗GPT 같은 기존 AI 시스템과 어떻게 다른지, 그리고 개발자 생태계에 어떤 근본적인 변화를 가져오고 있는지 알아보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;📌 Agentic AI란 무엇인가요?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Agentic AI(에이전틱 AI)&lt;/strong&gt; 는 사용자의 프롬프트에 단순히 대답만 하는 것을 넘어, &lt;strong&gt;주어진 목표를 달성하기 위해 스스로 계획(Planning)하고, 도구(Tools)를 사용하며, 환경과 상호작용하여 자율적으로 작업을 수행하는 AI 시스템&lt;/strong&gt;을 말합니다.&lt;/p&gt;
&lt;p&gt;쉽게 비유하자면, 기존의 AI가 &quot;답을 알려주는 똑똑한 백과사전이나 검색엔진&quot;이었다면, Agentic AI는 &quot;목표를 던져주면 알아서 판단하고 메일을 보내거나 코드를 수정하는 디지털 동료(Agent)&quot;에 가깝습니다.&lt;/p&gt;
&lt;h3&gt;🔑 Agentic AI의 4가지 핵심 요소&lt;/h3&gt;
&lt;p&gt;이러한 주도적인 동작을 가능하게 만드는 데는 크게 4가지 컴포넌트가 필요합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;지시 이해 및 목표 설정 (Goal Definition)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&quot;이 버튼 색상을 파란색으로 바꾸고, 관련된 테스트 코드를 작성한 뒤 PR을 올려줘&quot; 같은 크고 모호한 작업을 이해하고, 이를 달성하기 위해 필요한 구체적인 서브 태스크들로 세분화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;계획 및 추론 (Planning &amp;#x26; Reasoning)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;목표 달성을 위한 작업 순서를 스스로 계획합니다. 특히, 실행 중에 오류가 발생하면 원인을 추론하고 기존 계획을 수정하는 반성(Self-reflection) 과정을 거칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도구 활용 및 실행 (Tool Use &amp;#x26; Action)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;인터넷 검색, 터미널 명령 실행, 웹 브라우저 제어, API 호출, 파일 시스템 읽기/쓰기 등 다양한 외부 도구(Tools)를 능동적으로 활용해 물리적이거나 디지털적인 액션을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기억 (Memory)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;단기 기억(현재 진행 중인 작업의 컨텍스트)과 장기 기억(과거의 경험, 사용자의 작업 선호도 등)을 활용하여 일관되고 발전된 결과를 도출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;🆚 Generative AI vs Agentic AI&lt;/h2&gt;
&lt;p&gt;Agentic AI의 차별점은 기존 LLM 기반의 Generative AI와 비교해보면 명확해집니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Generative AI (기존 LLM)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Agentic AI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;작동 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;수동적 (Passive) - 질문에만 답함&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;능동적, 자율적 (Active, Autonomous)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;상호작용&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;단일 턴 (Single-turn) 응답 중심&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;다중 턴 (Multi-step) 작업 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;결과물&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;텍스트, 이미지, 단순 코드 조각 생성&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;실질적이고 독립적인 워크플로우 완료 (예: PR 생성)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;오류 처리&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;틀리면 인간이 프롬프트를 다시 입력해야 함&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;스스로 오류를 인지하고 디버깅하여 해결 재시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;도구 개입&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;제한적인 플러그인 의존 또는 없음&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;파일 시스템, 터미널, 브라우저 등 광범위한 직접 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Generative AI 시대의 목표가 &quot;AI와 대화를 잘하는 것(프롬프트 엔지니어링)&quot;이었다면, Agentic AI 시대의 핵심은 &quot;AI에게 일을 잘 위임하는 것&quot;으로 바뀌고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;💻 개발자 플랫폼과 생태계의 변화 사례&lt;/h2&gt;
&lt;p&gt;Agentic AI는 이미 소프트웨어 엔지니어링의 패러다임을 혁신적으로 바꾸고 있습니다. 대표적인 사례를 몇 가지 살펴보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자율 코딩 에이전트 (Autonomous Coding Agents)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;GitHub 이슈 링크나 짧은 요구사항만 주면, 에이전트가 코드 베이스 전체를 분석하고, 로직을 수정하며, 유닛 테스트를 실행하고, Git 커밋부터 최종 Pull Request까지 스스로 올립니다. (예: Devin, Claude Code 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동화된 QA 및 버그 헌팅&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;단순한 정적 분석기를 넘어 CI/CD 파이프라인과 통합됩니다. 코드가 푸시되면 에이전트가 브라우저를 열어 통합 테스트를 수행하고, 버그 발견 시 직접 픽스 코드를 제안하거나 적용합니다.&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 분석 및 리서치&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;여러 웹페이지를 크롤링하고 수집한 데이터를 분석하여 인사이트를 도출한 후, 잘 정리된 마크다운 보고서로 작성하여 로컬 디렉토리에 저장해냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;🚀 왜 지금 &apos;에이전틱 AI&apos;에 주목해야 할까요?&lt;/h2&gt;
&lt;p&gt;그동안 AI 업계는 모델 파라미터 크기 증가에 기반한 성능 향상(Scaling Law)에 크게 의존했습니다. 그러나 점차 한계 효용에 직면하면서, 모델 그 자체보다는 이 모델을 &lt;strong&gt;&apos;어떻게 활용할 것인가&apos;&lt;/strong&gt; 에 집중하기 시작했습니다.&lt;/p&gt;
&lt;p&gt;이 과정에서 에이전트가 제공하는 &lt;strong&gt;&apos;추론 시간 확장(Inference-time compute)&apos;&lt;/strong&gt; 개념은 복잡한 문제를 푸는 가장 현실적이고 강력한 돌파구로 부상했습니다. 모델이 학습 시 한 번에 똑똑해지기 어렵다면, 작업 내내 스스로 계획하고-수정하고-검증하는 사이클을 반복하게 만드는 것입니다.&lt;/p&gt;
&lt;p&gt;앞으로 개발자의 역할은 &quot;AI가 만들어준 코드를 복사해서 붙여넣는 프로그래머&quot;에서, 여러 AI 에이전트 시스템을 설계하고 감독(Supervision)하며 &lt;strong&gt;이들이 올바른 방향으로 협업하도록 이끄는 &apos;오케스트레이터&apos; 혹 매니저&lt;/strong&gt; 로 진화할 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;💡 결론&lt;/h2&gt;
&lt;p&gt;Agentic AI는 단순한 마케팅 유행어가 아닌, AI 발전의 피할 수 없는 &apos;다음 단계(Next Phase)&apos;입니다.&lt;/p&gt;
&lt;p&gt;개발자라면 이제는 단일 프롬프트를 깎는 노력에서 나아가, MCP(Model Context Protocol) 같은 최신 에이전트 인프라와 LangGraph, AutoGen 등의 프레임워크가 어떻게 작동하는지 이해하고, 이를 자신의 프로젝트나 조직의 워크플로우에 어떻게 통합할지 진지하게 고민해야 할 시점입니다.&lt;/p&gt;
&lt;p&gt;여러분은 에이전틱 AI를 활용해 어떤 반복 업무를 자동화해보고 싶으신가요? 댓글로 다양한 아이디어를 공유해주세요! 🛠️✨&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Secure Context 완전 정리 — HTTP에서 crypto.randomUUID가 안 되는 이유와 해결법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "분명 로컬에서는 잘 되는데, 개발서버에 올리니까  에러가 나요." 저도 최근에 정확히 이 상황을 겪었습니다. 로컬(localhost)에서는 멀쩡하게 동작하던 코드가 내부 IP…]]></description><link>https://blog.javapark.kr/secure-context-http-https/</link><guid isPermaLink="false">https://blog.javapark.kr/secure-context-http-https/</guid><pubDate>Mon, 06 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;분명 로컬에서는 잘 되는데, 개발서버에 올리니까 &lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID is not a function&lt;/code&gt; 에러가 나요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저도 최근에 정확히 이 상황을 겪었습니다. 로컬(localhost)에서는 멀쩡하게 동작하던 코드가 내부 IP로 접속하는 개발서버에서만 터지는 거죠. 원인은 단순합니다 — &lt;strong&gt;Secure Context&lt;/strong&gt; 때문입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;HTTP 환경에서는 브라우저가 일부 Web API를 아예 차단합니다.&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt;도 그 중 하나이고, 이것 말고도 꽤 많습니다. 오늘은 Secure Context가 뭔지, 어떤 API가 영향을 받는지, 그리고 어떻게 해결하는지 정리하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Secure Context란?&lt;/h2&gt;
&lt;p&gt;Secure Context는 브라우저가 &lt;strong&gt;&quot;이 환경은 보안이 보장된다&quot;고 판단하는 실행 컨텍스트&lt;/strong&gt;입니다. 쉽게 말하면, 브라우저가 &quot;여기서는 민감한 API를 써도 안전하다&quot;고 허용하는 조건이라고 보면 됩니다.&lt;/p&gt;
&lt;p&gt;핵심 판단 기준은 단 하나 — &lt;strong&gt;HTTPS로 서비스되고 있는가?&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HTTP vs HTTPS — 환경별 Secure Context 동작 차이&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;환경&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Secure Context&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;https://...&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ (브라우저 예외 허용)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;http://127.0.0.1&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ (브라우저 예외 허용)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;http://192.168.x.x&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ TypeError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;http://개발서버도메인&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ TypeError&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여기서 함정이 있습니다. &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;localhost&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;127.0.0.1&lt;/code&gt;은 HTTP여도 Secure Context로 인정됩니다.&lt;/strong&gt; 브라우저가 로컬 개발 편의를 위해 예외를 두고 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;그래서 로컬에서는 문제없이 동작하다가, 내부 IP(&lt;code class=&quot;language-text&quot;&gt;192.168.x.x&lt;/code&gt;)나 HTTP 도메인으로 서비스하는 개발서버에 배포하는 순간 터지는 겁니다. &quot;제 PC에서는 되는데요?&quot;의 전형적인 원인 중 하나입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Secure Context 전용 Web API 목록 — crypto.randomUUID만 있는 게 아닙니다&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt; 외에도 HTTP 환경에서 차단되는 API가 꽤 많습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;HTTP에서&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID 생성&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;crypto.subtle.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;암호화 (AES, RSA 등)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;navigator.geolocation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;위치 정보&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;navigator.mediaDevices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;카메라 / 마이크&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;navigator.clipboard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클립보드 읽기 / 쓰기&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ServiceWorker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PWA, 오프라인 캐시&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;WebAuthn&lt;/code&gt; (FIDO2)&lt;/td&gt;
&lt;td&gt;생체 인증&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 목록을 보면 알 수 있듯이, 하나의 API에서 문제가 발생했다면 &lt;strong&gt;다른 API에서도 같은 문제가 터질 가능성이 높습니다.&lt;/strong&gt; 하나씩 고치기보다는 근본적인 원인을 해결하는 게 맞습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;해결 방법 3가지&lt;/h2&gt;
&lt;h3&gt;방법 1. 개발서버 HTTPS 적용 (권장)&lt;/h3&gt;
&lt;p&gt;가장 근본적인 해결책입니다. HTTPS를 적용하면 모든 Secure Context API 문제가 한 번에 해결됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Kubernetes Ingress 예시&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;nginx.ingress.kubernetes.io/ssl-redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; dev.example.com
    &lt;span class=&quot;token key atrule&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tls&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;secret&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;내부 개발서버라면 자체 서명 인증서(self-signed)나 mkcert 같은 도구로 간단하게 적용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;방법 2. 코드 레벨 폴백 — 간단하지만 실용적&lt;/h3&gt;
&lt;p&gt;UUID 형식이 반드시 필요하지 않은 경우, 간단한 폴백으로 해결할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;generateId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  globalThis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crypto&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;randomUUID&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt;
  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;HTTPS / HTTP 모두 동작&lt;/li&gt;
&lt;li&gt;고유성은 보장되지만 RFC 4122 UUID 형식은 아님&lt;/li&gt;
&lt;li&gt;임시 ID, 클라이언트 사이드 키 등에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;방법 3. crypto.getRandomValues() 사용 — UUID 형식이 필요할 때&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;randomUUID()&lt;/code&gt;는 Secure Context 전용이지만, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;getRandomValues()&lt;/code&gt;는 HTTP에서도 동작합니다.&lt;/strong&gt; RFC 4122 v4 형식의 UUID가 필요하다면 이 방법을 씁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; generateUUIDv4 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;globalThis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crypto&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;randomUUID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; globalThis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bytes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  globalThis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRandomValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  bytes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x0f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x40&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// version 4&lt;/span&gt;
  bytes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x3f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x80&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// variant&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;bytes&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;어떤 방법을 써야 할까?&lt;/h3&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;개발서버/스테이징 환경 전체에 적용&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;방법 1&lt;/strong&gt; (HTTPS 적용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빠르게 에러만 해결하고 싶을 때&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;방법 2&lt;/strong&gt; (폴백)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID v4 형식이 반드시 필요한 로직&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;방법 3&lt;/strong&gt; (getRandomValues)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기적으로는?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;방법 1 + 방법 2 또는 3&lt;/strong&gt; 조합&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Secure Context 확인하는 방법&lt;/h2&gt;
&lt;p&gt;현재 환경이 Secure Context인지 브라우저 콘솔에서 바로 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSecureContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// true 또는 false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;개발 중 &quot;이 API가 왜 안 되지?&quot; 싶을 때, 가장 먼저 확인해볼 값입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ — 자주 묻는 질문&lt;/h2&gt;
&lt;h3&gt;Q. localhost는 HTTP인데 왜 Secure Context인가요?&lt;/h3&gt;
&lt;p&gt;브라우저가 로컬 개발 편의를 위해 &lt;code class=&quot;language-text&quot;&gt;localhost&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;127.0.0.1&lt;/code&gt;을 &lt;strong&gt;명시적으로 예외 처리&lt;/strong&gt;하고 있습니다. W3C Secure Contexts 스펙에 정의된 동작이며, 모든 주요 브라우저(Chrome, Firefox, Safari, Edge)에서 동일하게 적용됩니다.&lt;/p&gt;
&lt;h3&gt;Q. 자체 서명 인증서(self-signed)로도 Secure Context가 되나요?&lt;/h3&gt;
&lt;p&gt;네, HTTPS로 서비스되면 인증서 종류와 관계없이 Secure Context입니다. 다만 브라우저에서 &quot;안전하지 않음&quot; 경고가 뜹니다. 내부 개발용이라면 &lt;code class=&quot;language-text&quot;&gt;mkcert&lt;/code&gt;로 로컬 CA 인증서를 만들면 경고 없이 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q. crypto.getRandomValues()는 왜 HTTP에서도 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;getRandomValues()&lt;/code&gt;는 Secure Context 이전부터 존재하던 API이고, 난수 생성 자체는 보안 위협이 아니기 때문입니다. 반면 &lt;code class=&quot;language-text&quot;&gt;randomUUID()&lt;/code&gt;는 나중에 추가된 API로, 처음부터 Secure Context 전용으로 설계되었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;정리하면 이렇습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;개발서버  →  HTTPS 적용 (Ingress TLS, mkcert 등)
코드 내   →  폴백 패턴 유지 (방어적 코딩)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HTTP 환경에서 발생하는 Secure Context 오류는 하나씩 발견되기보다 &lt;strong&gt;개발서버에 HTTPS를 적용하는 것이 가장 확실하고 포괄적인 해결책&lt;/strong&gt;입니다. 한 번 적용해두면 &lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt; 뿐 아니라 클립보드, 위치 정보, Service Worker 등에서도 같은 문제가 재발하지 않습니다.&lt;/p&gt;
&lt;p&gt;여러분도 혹시 &quot;로컬에서는 되는데 서버에서는 안 되는&quot; 경험이 있으신가요? 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[JMeter + Docker로 성능 테스트하기 — 설치 없이 바로 실행하는 부하 테스트 가이드]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "API 배포 전에 성능 테스트 좀 돌려봐야 하는데, JMeter 설치부터 귀찮습니다." 솔직히 저도 그랬습니다. JMeter는 강력한 도구지만, Java 설치 → JMeter…]]></description><link>https://blog.javapark.kr/jmeter-docker-performance-testing/</link><guid isPermaLink="false">https://blog.javapark.kr/jmeter-docker-performance-testing/</guid><pubDate>Mon, 06 Apr 2026 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;API 배포 전에 성능 테스트 좀 돌려봐야 하는데, JMeter 설치부터 귀찮습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 그랬습니다. JMeter는 강력한 도구지만, Java 설치 → JMeter 다운로드 → 환경변수 설정 → 플러그인 설치... 시작하기까지가 꽤 번거롭습니다. 팀원마다 환경이 달라서 &quot;제 PC에서는 되는데요?&quot;라는 말도 자주 나오고요.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Docker 하나면 이 문제가 해결됩니다.&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 한 줄이면 JMeter가 바로 실행되고, Docker Compose로 분산 테스트까지 가능합니다. 오늘은 Docker 기반 JMeter 성능 테스트를 처음부터 끝까지 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 JMeter + Docker인가?&lt;/h2&gt;
&lt;p&gt;먼저 왜 Docker로 JMeter를 실행해야 하는지 정리하겠습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기존 방식&lt;/th&gt;
&lt;th&gt;Docker 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Java + JMeter 직접 설치&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 한 줄로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;팀원마다 버전·환경 다름&lt;/td&gt;
&lt;td&gt;동일한 이미지로 환경 통일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분산 테스트 구성 복잡&lt;/td&gt;
&lt;td&gt;Docker Compose로 간단 스케일링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD 연동 시 설치 스크립트 필요&lt;/td&gt;
&lt;td&gt;이미지 pull만 하면 끝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트 후 정리 필요&lt;/td&gt;
&lt;td&gt;컨테이너 종료 시 자동 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심은 &lt;strong&gt;재현 가능한 환경&lt;/strong&gt;입니다. 누가, 어디서, 언제 실행해도 동일한 조건에서 테스트할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1단계: Docker로 JMeter 바로 실행하기&lt;/h2&gt;
&lt;h3&gt;Docker 이미지 선택&lt;/h3&gt;
&lt;p&gt;2026년 현재 주요 JMeter Docker 이미지를 비교하면:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이미지&lt;/th&gt;
&lt;th&gt;JMeter 버전&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;justb4/jmeter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5.5&lt;/td&gt;
&lt;td&gt;가장 인기 있지만 2022년 이후 업데이트 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;qainsights/jmeter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5.6.3&lt;/td&gt;
&lt;td&gt;최신 버전, multi-arch, 비root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커스텀 빌드&lt;/td&gt;
&lt;td&gt;원하는 버전&lt;/td&gt;
&lt;td&gt;플러그인 포함 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;추천:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;qainsights/jmeter:5.6.3&lt;/code&gt; 또는 직접 Dockerfile 작성&lt;/p&gt;
&lt;h3&gt;가장 간단한 실행&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 테스트 계획 실행 + HTML 리포트 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/tests:/jmeter/tests &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/results:/jmeter/results &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  qainsights/jmeter:5.6.3 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; /jmeter/tests/test-plan.jmx &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; /jmeter/results/results.jtl &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /jmeter/results/html-report&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;각 옵션의 의미:&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;--rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트 끝나면 컨테이너 자동 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호스트의 테스트 파일과 결과 디렉토리 마운트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Non-GUI 모드&lt;/strong&gt; (Docker에서는 필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트 계획 파일 (.jmx)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;결과 로그 파일 (.jtl)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-e -o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTML 리포트 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;주의:&lt;/strong&gt; JMeter GUI는 Docker에서 사용하지 않습니다. GUI는 로컬에서 테스트 계획(.jmx)을 만들 때만 사용하고, 실행은 항상 Non-GUI 모드(&lt;code class=&quot;language-text&quot;&gt;-n&lt;/code&gt;)로 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;폴더 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;my-project/
├── tests/
│   ├── test-plan.jmx          # JMeter 테스트 계획
│   ├── test-data.csv          # 테스트 데이터 (선택)
│   └── user.properties        # 추가 설정 (선택)
├── results/                   # 테스트 결과 (자동 생성)
│   ├── results.jtl
│   └── html-report/
│       └── index.html         # 브라우저로 열어서 확인
└── docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;2단계: 테스트 계획 (.jmx) 작성하기&lt;/h2&gt;
&lt;p&gt;JMeter GUI에서 만드는 게 가장 편하지만, 구조를 이해하면 수정이 쉬워집니다. REST API 테스트 기본 구조를 정리하겠습니다.&lt;/p&gt;
&lt;h3&gt;핵심 컴포넌트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Test Plan (테스트 계획)
├── Thread Group (사용자 시뮬레이션)
│   ├── HTTP Request Defaults (공통 설정)
│   ├── HTTP Header Manager (헤더)
│   ├── HTTP Request — GET /api/users
│   │   ├── Response Assertion (응답 검증)
│   │   └── JSON Extractor (값 추출)
│   ├── HTTP Request — POST /api/users
│   └── Summary Report (결과 요약)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Thread Group 설정 — 부하 패턴 결정&lt;/h3&gt;
&lt;p&gt;Thread Group은 &lt;strong&gt;몇 명의 가상 사용자가, 얼마 동안, 어떤 패턴으로&lt;/strong&gt; 요청을 보낼지 결정합니다.&lt;/p&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;Number of Threads&lt;/td&gt;
&lt;td&gt;동시 사용자 수&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ramp-Up Period&lt;/td&gt;
&lt;td&gt;모든 사용자가 시작되기까지 걸리는 시간&lt;/td&gt;
&lt;td&gt;10초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;테스트 총 시간&lt;/td&gt;
&lt;td&gt;60초&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;팁: 변수화하면 Docker에서 유연하게 사용할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- jmx 파일 내부 --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;intProp&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ThreadGroup.num_threads&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;${__P(threads,10)}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;intProp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;stringProp&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ThreadGroup.duration&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;${__P(duration,60)}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;stringProp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 Docker 실행 시 값을 주입할 수 있습니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. qainsights/jmeter:5.6.3 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; /jmeter/tests/test-plan.jmx &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-Jthreads&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-Jduration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-Jthreads=100&lt;/code&gt;이 &lt;code class=&quot;language-text&quot;&gt;.jmx&lt;/code&gt; 내부의 &lt;code class=&quot;language-text&quot;&gt;${__P(threads,10)}&lt;/code&gt;을 100으로 오버라이드합니다. 기본값은 10이므로, 값을 안 넘기면 10명의 가상 사용자로 실행됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3단계: 커스텀 Docker 이미지 만들기&lt;/h2&gt;
&lt;p&gt;프로젝트에서 반복적으로 사용한다면, 플러그인과 설정을 포함한 커스텀 이미지를 만드는 게 편합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; eclipse-temurin:17-jre-alpine&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ARG&lt;/span&gt; JMETER_VERSION=5.6.3&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; JMETER_HOME=/opt/apache-jmeter-&lt;span class=&quot;token variable&quot;&gt;${JMETER_VERSION}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; PATH=&lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/bin:&lt;span class=&quot;token variable&quot;&gt;${PATH}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; HEAP=&lt;span class=&quot;token string&quot;&gt;&quot;-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m&quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# JMeter 설치&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; apk add --no-cache curl unzip bash &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    &amp;amp;&amp;amp; curl -L https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-&lt;span class=&quot;token variable&quot;&gt;${JMETER_VERSION}&lt;/span&gt;.tgz &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
       | tar xzf - -C /opt &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    &amp;amp;&amp;amp; rm -rf &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/docs &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/printable_docs&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 플러그인 매니저 설치&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; curl -L -o &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/lib/ext/jmeter-plugins-manager.jar &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    https://jmeter-plugins.org/get/ &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    &amp;amp;&amp;amp; curl -L -o &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/lib/cmdrunner-2.3.jar &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    https://repo1.maven.org/maven2/kg/apc/cmdrunner/2.3/cmdrunner-2.3.jar &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    &amp;amp;&amp;amp; java -cp &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/lib/ext/jmeter-plugins-manager.jar &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
       org.jmeterplugins.repository.PluginManagerCMDInstaller&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 자주 쓰는 플러그인 설치&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${JMETER_HOME}&lt;/span&gt;/bin/PluginsManagerCMD.sh install &lt;span class=&quot;token operator&quot;&gt;\&lt;/span&gt;
    jpgc-casutg,jpgc-tst,jpgc-graphs-basic,jpgc-json,jpgc-perfmon&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; /jmeter&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;jmeter&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 빌드&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; build &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; my-jmeter:5.6.3 &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 실행&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/tests:/jmeter/tests &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/results:/jmeter/results &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  my-jmeter:5.6.3 &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; /jmeter/tests/test-plan.jmx &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; /jmeter/results/results.jtl &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /jmeter/results/html-report&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;포함한 플러그인 설명&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;jpgc-casutg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;유연한 부하 패턴 (Stepping, Ultimate Thread Group)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jpgc-tst&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;초당 요청수(TPS) 정밀 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jpgc-graphs-basic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;응답 시간, TPS 그래프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jpgc-json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON 응답 추출·검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jpgc-perfmon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서버 CPU/메모리/디스크 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;4단계: Docker Compose로 분산 테스트&lt;/h2&gt;
&lt;p&gt;한 대의 컨테이너로는 충분한 부하를 만들기 어려울 수 있습니다. Docker Compose로 &lt;strong&gt;마스터 1대 + 슬레이브 N대&lt;/strong&gt; 구조를 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;jmeter-slave-1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; qainsights/jmeter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;5.6.3
    &lt;span class=&quot;token key atrule&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;slave&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;entrypoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;server
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Jserver.rmi.ssl.disable=true
    &lt;span class=&quot;token key atrule&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1099&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;50000&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net

  &lt;span class=&quot;token key atrule&quot;&gt;jmeter-slave-2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; qainsights/jmeter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;5.6.3
    &lt;span class=&quot;token key atrule&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;slave&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;entrypoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;server
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Jserver.rmi.ssl.disable=true
    &lt;span class=&quot;token key atrule&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1099&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;50000&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net

  &lt;span class=&quot;token key atrule&quot;&gt;jmeter-master&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; qainsights/jmeter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;5.6.3
    &lt;span class=&quot;token key atrule&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;master
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./tests&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/jmeter/tests
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./results&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/jmeter/results
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; HEAP=&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Xms1g &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Xmx2g
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
      -n -t /jmeter/tests/test-plan.jmx
      -l /jmeter/results/results.jtl
      -e -o /jmeter/results/html-report
      -R jmeter-slave-1,jmeter-slave-2
      -Jserver.rmi.ssl.disable=true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;depends_on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;slave&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;slave&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net

&lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;jmeter-net&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bridge&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 실행&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; compose up

&lt;span class=&quot;token comment&quot;&gt;# 슬레이브 5대로 스케일링&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; compose up &lt;span class=&quot;token parameter variable&quot;&gt;--scale&lt;/span&gt; jmeter-slave-1&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;분산 테스트 핵심 포인트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;-R&lt;/code&gt; 옵션:&lt;/strong&gt; 슬레이브 호스트명을 쉼표로 구분하여 지정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;-Jserver.rmi.ssl.disable=true&lt;/code&gt;:&lt;/strong&gt; Docker 내부 네트워크에서는 SSL 불필요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포트 1099, 50000:&lt;/strong&gt; RMI 통신에 필요한 포트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네트워크:&lt;/strong&gt; Docker 네트워크 안에서 서비스명으로 통신 (DNS 자동 해석)&lt;/li&gt;
&lt;li&gt;슬레이브에서 Thread 50개 설정 + 슬레이브 4대 = &lt;strong&gt;총 200명의 가상 사용자&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5단계: HTML 리포트 확인하기&lt;/h2&gt;
&lt;p&gt;테스트가 끝나면 &lt;code class=&quot;language-text&quot;&gt;results/html-report/index.html&lt;/code&gt;을 브라우저로 열면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 테스트 실행 후 결과 확인&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt; results/html-report/index.html    &lt;span class=&quot;token comment&quot;&gt;# macOS&lt;/span&gt;
start results/html-report/index.html   &lt;span class=&quot;token comment&quot;&gt;# Windows&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HTML 리포트에 포함되는 주요 지표:&lt;/p&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;&lt;strong&gt;Average Response Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;평균 응답 시간&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;90th Percentile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;90%의 요청이 이 시간 이내&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 500ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실패한 요청 비율&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;초당 처리 요청 수 (TPS)&lt;/td&gt;
&lt;td&gt;목표에 따라 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;기존 .jtl 파일에서 리포트 재생성&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;:/jmeter qainsights/jmeter:5.6.3 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; /jmeter/results/results.jtl &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /jmeter/results/html-report-v2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-g&lt;/code&gt; 옵션으로 이미 생성된 &lt;code class=&quot;language-text&quot;&gt;.jtl&lt;/code&gt; 파일에서 리포트만 다시 만들 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6단계: CI/CD 파이프라인에 연동하기&lt;/h2&gt;
&lt;p&gt;Docker 기반이라 CI/CD 연동이 매우 간단합니다.&lt;/p&gt;
&lt;h3&gt;GitHub Actions 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Performance Test

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;threads&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;동시 사용자 수&quot;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;테스트 시간 (초)&quot;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;60&quot;&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;load-test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Run JMeter
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          docker run --rm \
            -v ${{ github.workspace }}/tests:/jmeter/tests \
            -v ${{ github.workspace }}/results:/jmeter/results \
            qainsights/jmeter:5.6.3 \
            -n -t /jmeter/tests/api-test.jmx \
            -l /jmeter/results/results.jtl \
            -e -o /jmeter/results/html-report \
            -Jthreads=${{ github.event.inputs.threads }} \
            -Jduration=${{ github.event.inputs.duration }}&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Upload Report
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;artifact@v4
        &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always()
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;report
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; results/html&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;report/

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Check Error Rate
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          ERROR_RATE=$(tail -n +2 results/results.jtl | \
            awk -F&apos;,&apos; &apos;{total++; if($8==&quot;false&quot;) errors++} END {printf &quot;%.2f&quot;, (errors/total)*100}&apos;)
          echo &quot;Error rate: ${ERROR_RATE}%&quot;
          if (( $(echo &quot;$ERROR_RATE &gt; 5.0&quot; | bc -l) )); then
            echo &quot;::error::Error rate ${ERROR_RATE}% exceeds 5% threshold&quot;
            exit 1
          fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;포인트:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;workflow_dispatch&lt;/code&gt;로 수동 트리거하면서 사용자 수와 시간을 파라미터로 넘길 수 있습니다. 에러율이 5%를 넘으면 파이프라인이 실패합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 팁 — 자주 하는 실수와 해결법&lt;/h2&gt;
&lt;h3&gt;1. &quot;리포트 디렉토리가 이미 있습니다&quot; 오류&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR: org.apache.jmeter.JMeter: An error occurred:
Cannot write to &apos;/jmeter/results/html-report&apos; as folder is not empty&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;해결:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;-o&lt;/code&gt; 경로는 비어있거나 존재하지 않아야 합니다. 매번 삭제하거나 타임스탬프를 붙이세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 실행 전 삭제&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-rf&lt;/span&gt; results/html-report

&lt;span class=&quot;token comment&quot;&gt;# 또는 타임스탬프 사용&lt;/span&gt;
&lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /jmeter/results/report-&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. 메모리 부족 (OutOfMemoryError)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# JVM 힙 사이즈 설정&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;HEAP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-Xms2g -Xmx4g&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--memory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;6g &lt;span class=&quot;token parameter variable&quot;&gt;--cpus&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;경험칙:&lt;/strong&gt; 컨테이너당 가상 사용자 250~500명 정도가 적당합니다. 그 이상은 슬레이브를 늘리세요.&lt;/p&gt;
&lt;h3&gt;3. Docker 네트워크에서 대상 서비스 접근&lt;/h3&gt;
&lt;p&gt;테스트 대상이 같은 Docker 네트워크에 있다면 서비스명으로 접근 가능합니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;my-api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net

  &lt;span class=&quot;token key atrule&quot;&gt;jmeter-master&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
      -n -t /jmeter/tests/test.jmx
      -Jhost=my-api -Jport=8080&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; jmeter&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4. Windows 환경에서 볼륨 마운트&lt;/h3&gt;
&lt;p&gt;Windows에서는 &lt;code class=&quot;language-text&quot;&gt;$(pwd)&lt;/code&gt; 대신 절대 경로를 사용하세요:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# PowerShell&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token environment constant&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/tests:/jmeter/tests &lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;PWD&lt;/span&gt;}&lt;/span&gt;/results:/jmeter/results `
  qainsights/jmeter:5.6.3 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.

&lt;span class=&quot;token comment&quot;&gt;# CMD&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; ^
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; %cd%/tests:/jmeter/tests ^
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; %cd%/results:/jmeter/results ^
  qainsights/jmeter:5.6.3 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;정리 — 전체 워크플로우&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. 테스트 계획 작성    →  JMeter GUI (로컬)에서 .jmx 파일 생성
2. Docker로 실행      →  docker run / docker compose up
3. 리포트 확인        →  results/html-report/index.html
4. CI/CD 연동        →  GitHub Actions / Jenkins에서 자동 실행
5. 분산 테스트        →  Docker Compose로 슬레이브 스케일링&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Docker 기반 JMeter의 가장 큰 장점은 **&quot;환경 문제 없이 누구나 동일한 테스트를 실행할 수 있다&quot;**는 것입니다. 팀 전체가 같은 이미지를 쓰면 &quot;제 PC에서는 되는데요?&quot; 문제가 사라집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ — 자주 묻는 질문&lt;/h2&gt;
&lt;h3&gt;Q. JMeter GUI를 Docker에서 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;기술적으로는 X11 포워딩으로 가능하지만, 권장하지 않습니다. GUI는 로컬에 설치해서 테스트 계획(.jmx)만 만들고, 실행은 Docker에서 Non-GUI 모드로 하는 게 표준적인 방식입니다.&lt;/p&gt;
&lt;h3&gt;Q. Gatling이나 k6 대신 JMeter를 써야 하는 이유가 있나요?&lt;/h3&gt;
&lt;p&gt;JMeter는 GUI로 테스트를 만들 수 있어서 &lt;strong&gt;코딩 없이 시작할 수 있다&lt;/strong&gt;는 게 가장 큰 장점입니다. Gatling(Scala)이나 k6(JavaScript)는 코드로 시나리오를 작성해야 합니다. 반면 대규모 테스트에서는 k6이 메모리 효율이 더 좋습니다. 팀의 기술 스택과 요구사항에 맞게 선택하면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q. 분산 테스트에서 슬레이브를 몇 대까지 늘릴 수 있나요?&lt;/h3&gt;
&lt;p&gt;Docker Compose 기준으로 한 호스트에서 5~10대 정도가 현실적입니다. 그 이상은 호스트 자체의 CPU/메모리가 병목이 됩니다. 더 큰 규모가 필요하면 Kubernetes나 AWS ECS에서 슬레이브를 분산 배포하는 것을 고려하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 성능 테스트를 어떤 도구로 하고 계신가요? JMeter 외에 다른 도구 경험이 있다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude Code 토큰 절약 가이드 [고급편] — 자동화와 아키텍처 레벨 최적화]]></title><description><![CDATA[Claude Code 토큰 절약 가이드 고급편 커버 — 자동화·아키텍처 레벨 최적화와 비용 모니터링 안녕하세요, 자바파커입니다. "설정도 다 했고, 습관도 잡았는데... 팀 전체의 Claude Code…]]></description><link>https://blog.javapark.kr/token-saving-advanced/</link><guid isPermaLink="false">https://blog.javapark.kr/token-saving-advanced/</guid><pubDate>Sun, 05 Apr 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-token-saving-advanced.png&quot; alt=&quot;Claude Code 토큰 절약 가이드 고급편 커버 — 자동화·아키텍처 레벨 최적화와 비용 모니터링&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;설정도 다 했고, 습관도 잡았는데... 팀 전체의 Claude Code 비용을 체계적으로 관리하고 싶어요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;초급편에서 기본 습관을, 중급편에서 설정 최적화를 다뤘습니다. 고급편에서는 &lt;strong&gt;시스템 차원의 토큰 관리 전략&lt;/strong&gt;을 정리합니다. 개인 사용뿐 아니라 팀·프로젝트 단위로 비용을 통제하는 방법까지 포함합니다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;strong&gt;[초급 → 중급 → 고급] 시리즈의 마지막 편&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. Memory 시스템으로 반복 설명 완전 제거&lt;/h2&gt;
&lt;p&gt;CLAUDE.md가 &quot;프로젝트 규칙서&quot;라면, Memory는 **&quot;나와 Claude 사이의 공유 기억&quot;**입니다.&lt;/p&gt;
&lt;h3&gt;Memory란?&lt;/h3&gt;
&lt;p&gt;Claude Code의 Memory 시스템은 대화가 끝나도 유지되는 영구 저장소입니다. 사용자 선호, 프로젝트 맥락, 피드백 등을 파일로 저장해두면 &lt;strong&gt;새 세션에서도 자동으로 참조&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;~/.claude/projects/{프로젝트}/memory/
├── MEMORY.md          ← 인덱스 (자동 로드)
├── user_profile.md    ← 사용자 정보
├── feedback_style.md  ← 피드백/선호
└── project_arch.md    ← 프로젝트 맥락&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;토큰 절약에 어떻게 도움이 되나?&lt;/h3&gt;
&lt;p&gt;Memory가 없으면 매 세션마다 반복하게 되는 설명들:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 매 세션 시작마다 이런 말을 하고 있다면...
&quot;이 프로젝트는 Next.js 14 + TypeScript야.
 DB는 PostgreSQL이고 Prisma 쓰고 있어.
 테스트는 vitest, 배포는 Vercel.
 코드 스타일은 airbnb 규칙 따르고...&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이걸 Memory에 한 번 저장하면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;이번에 기억해줘&quot; 라고 말하면 됩니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이후 세션에서는 이 정보가 자동으로 로드됩니다. &lt;strong&gt;매번 50~100토큰씩 절약&lt;/strong&gt;되고, 세션이 쌓일수록 누적 효과가 큽니다.&lt;/p&gt;
&lt;h3&gt;Memory 작성 팁&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- memory/project_stack.md --&gt;&lt;/span&gt;

name: 프로젝트 기술 스택
description: 현재 프로젝트의 기술 스택과 규칙
type: project

&lt;span class=&quot;token hr punctuation&quot;&gt;---&lt;/span&gt;

Next.js 14 App Router + TypeScript strict mode
DB: PostgreSQL + Prisma ORM
테스트: vitest + Testing Library
배포: Vercel
코드 스타일: ESLint airbnb + Prettier&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;핵심:&lt;/strong&gt; Memory 파일도 MEMORY.md 인덱스를 통해 매번 로드되므로, &lt;strong&gt;짧고 핵심만&lt;/strong&gt; 담아야 합니다. Memory가 길어지면 오히려 토큰 낭비입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. Skill로 프롬프트 토큰 최소화&lt;/h2&gt;
&lt;p&gt;Skill은 &lt;strong&gt;반복되는 작업 지시를 파일로 저장해 슬래시 명령어로 호출&lt;/strong&gt;하는 기능입니다.&lt;/p&gt;
&lt;h3&gt;토큰 관점에서 Skill의 가치&lt;/h3&gt;
&lt;p&gt;Skill 없이 매번 입력하는 경우:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;블로그 포스팅 써줘.
톤은 &quot;~합니다&quot; 체로, 친근하지만 가볍지 않게.
&quot;안녕하세요, 자바파커입니다&quot;로 시작.
마크다운으로 작성하고, 구분선으로 섹션 나누고,
표와 코드 블록 적극 활용하고,
마지막에 댓글 유도 질문 넣어줘.
SEO 태그도 넣고...
(~200토큰)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Skill로 호출하면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/blog-post Claude Code 토큰 절약법
(~10토큰)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;매 호출마다 ~190토큰 절약.&lt;/strong&gt; Skill 파일은 호출 시에만 로드되므로, 사용하지 않을 때는 컨텍스트를 차지하지 않습니다.&lt;/p&gt;
&lt;h3&gt;Skill 최적화 작성법&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- .claude/skills/code-review.md --&gt;&lt;/span&gt;

name: code-review
description: PR 코드 리뷰 수행

&lt;span class=&quot;token hr punctuation&quot;&gt;---&lt;/span&gt;

변경된 파일을 분석하고 아래 항목만 체크:

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; 버그 가능성
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; 성능 이슈
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; 보안 취약점

각 항목 발견 시 파일:라인 형식으로 보고.
문제 없으면 &quot;LGTM&quot; 한 줄만 출력.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;팁:&lt;/strong&gt; Skill 내부 지시도 간결하게. &quot;친절하게 설명해줘&quot; 같은 문구 대신, 출력 형식을 명확히 지정하면 응답 토큰도 줄어듭니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 워크트리(Worktree) 격리로 컨텍스트 분리&lt;/h2&gt;
&lt;p&gt;워크트리는 &lt;strong&gt;같은 저장소의 독립적인 작업 복사본&lt;/strong&gt;을 만드는 Git 기능입니다. Claude Code에서는 서브에이전트를 워크트리에서 실행하여 컨텍스트를 완전히 분리할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;워크트리가 토큰을 줄이는 원리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;메인 세션 (feature-A 작업 중)
  │
  ├── /clear 없이 feature-B 작업 시작
  │   → feature-A 컨텍스트가 계속 누적 (토큰 낭비)
  │
  └── 워크트리에서 feature-B 실행
      → 완전히 독립된 컨텍스트 (토큰 절약)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;실전 활용&lt;/h3&gt;
&lt;p&gt;Claude Code에서 서브에이전트를 워크트리 모드로 실행하면, 해당 작업은 별도의 Git 브랜치에서 독립적으로 진행됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;feature-B를 별도 워크트리에서 구현해줘.
 메인 브랜치에 영향 없이 작업하고 결과만 알려줘.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 메인 대화의 컨텍스트를 오염시키지 않으면서 병렬 작업이 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. max-turns로 자동 실행 제한&lt;/h2&gt;
&lt;p&gt;Claude Code가 자율적으로 작업할 때, 예상보다 많은 턴을 사용하는 경우가 있습니다. &lt;code class=&quot;language-text&quot;&gt;max-turns&lt;/code&gt; 옵션으로 이를 제한할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# CLI에서 턴 수 제한&lt;/span&gt;
claude --max-turns &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이 버그 수정해줘&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;왜 턴 제한이 필요한가?&lt;/h3&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;단순 버그 수정&lt;/td&gt;
&lt;td&gt;3~5턴&lt;/td&gt;
&lt;td&gt;적음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽질하는 Claude&lt;/td&gt;
&lt;td&gt;20~50턴&lt;/td&gt;
&lt;td&gt;매우 많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;무한 루프 (드물지만 발생)&lt;/td&gt;
&lt;td&gt;100턴+&lt;/td&gt;
&lt;td&gt;폭발&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;max-turns&lt;/code&gt;를 설정하면 Claude가 지정된 턴 내에서 작업을 마치지 못하면 중단됩니다. 이때 상황을 파악하고, 더 구체적인 지시를 주거나 접근 방식을 바꿀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장 설정:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;작업 유형&lt;/th&gt;
&lt;th&gt;권장 max-turns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단순 수정&lt;/td&gt;
&lt;td&gt;5~10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기능 구현&lt;/td&gt;
&lt;td&gt;15~25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대규모 리팩토링&lt;/td&gt;
&lt;td&gt;30~50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;5. 비용 모니터링과 예산 설정&lt;/h2&gt;
&lt;h3&gt;API 사용량 모니터링&lt;/h3&gt;
&lt;p&gt;Anthropic Console에서 API 키별 사용량을 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 현재 세션 비용 확인&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Claude Code 내에서&lt;/span&gt;
/cost&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;예산 제한 설정&lt;/h3&gt;
&lt;p&gt;API 사용 시 월별 예산 한도를 설정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Anthropic Console에서 설정&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Settings → Plans &amp;amp; Billing → Usage Limits&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 월별 사용 한도를 설정하면 초과 시 API 호출이 차단됨&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;팀 단위 비용 관리&lt;/h3&gt;
&lt;p&gt;팀에서 Claude Code를 사용한다면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API 키 분리&lt;/strong&gt; — 프로젝트별/팀원별 API 키를 분리하여 사용량 추적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예산 알림&lt;/strong&gt; — 사용량이 임계치(80%)에 도달하면 알림 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용 가이드 공유&lt;/strong&gt; — 이 시리즈의 초급/중급 체크리스트를 팀 위키에 공유&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;6. CI/CD 파이프라인에서의 토큰 최적화&lt;/h2&gt;
&lt;p&gt;Claude Code를 CI/CD에서 자동화 도구로 사용하는 경우, 토큰 관리가 특히 중요합니다.&lt;/p&gt;
&lt;h3&gt;CI에서의 토큰 절약 전략&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# GitHub Actions 예시 — 필요한 작업만 정확히 지정&lt;/span&gt;
claude --max-turns &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--model&lt;/span&gt; claude-sonnet-4-6 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;src/ 디렉토리의 변경된 파일에 대해 코드 리뷰하고
   문제가 있으면 GitHub 코멘트로 남겨줘.
   문제 없으면 &apos;LGTM&apos;만 출력.&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;CI/CD 체크리스트:&lt;/strong&gt;&lt;/p&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;모델&lt;/td&gt;
&lt;td&gt;Sonnet 또는 Haiku (Opus 불필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max-turns&lt;/td&gt;
&lt;td&gt;작업별 최소값 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프롬프트&lt;/td&gt;
&lt;td&gt;출력 형식을 명확히 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.claudeignore&lt;/td&gt;
&lt;td&gt;테스트/빌드 산출물 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;7. 전체 토큰 최적화 아키텍처&lt;/h2&gt;
&lt;p&gt;지금까지 초급·중급·고급에서 다룬 모든 전략을 하나의 구조로 정리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────────────────────────────────────────┐
│           토큰 최적화 아키텍처                  │
├─────────────────────────────────────────────┤
│                                             │
│  [Layer 1: 습관] ← 초급편                    │
│   ├── /compact &amp;amp; /clear                     │
│   ├── 간결한 프롬프트                         │
│   └── 작업 단위 분리                          │
│                                             │
│  [Layer 2: 설정] ← 중급편                    │
│   ├── .claudeignore                         │
│   ├── autocompact                           │
│   ├── 모델 전환 전략                          │
│   └── Hook 자동화                            │
│                                             │
│  [Layer 3: 시스템] ← 고급편                   │
│   ├── Memory 시스템                          │
│   ├── Skill 최적화                           │
│   ├── 워크트리 격리                           │
│   ├── max-turns 제한                         │
│   └── 비용 모니터링                           │
│                                             │
└─────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;적용 우선순위&lt;/h3&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;ROI (투자 대비 효과)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;/clear + 세션 분리&lt;/td&gt;
&lt;td&gt;최고 — 0초 투자, 즉각 효과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;.claudeignore&lt;/td&gt;
&lt;td&gt;높음 — 5분 설정, 영구 효과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;autocompact&lt;/td&gt;
&lt;td&gt;높음 — 1분 설정, 영구 효과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;모델 전환 전략&lt;/td&gt;
&lt;td&gt;높음 — 습관만 들이면 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;간결한 프롬프트&lt;/td&gt;
&lt;td&gt;중간 — 꾸준한 의식 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Memory 시스템&lt;/td&gt;
&lt;td&gt;중간 — 초기 설정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Skill 파일&lt;/td&gt;
&lt;td&gt;중간 — 반복 작업이 있을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;max-turns&lt;/td&gt;
&lt;td&gt;중간 — 자율 실행 시 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;워크트리 격리&lt;/td&gt;
&lt;td&gt;상황적 — 병렬 작업 시 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;CI/CD 최적화&lt;/td&gt;
&lt;td&gt;상황적 — 자동화 사용 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;고급편 요약 — 최종 체크리스트&lt;/h2&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;th&gt;적용 난이도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Memory 시스템&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Skill 최적화&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;워크트리 격리&lt;/td&gt;
&lt;td&gt;★★★☆☆&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;max-turns 제한&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;비용 모니터링&lt;/td&gt;
&lt;td&gt;★★★☆☆&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;CI/CD 최적화&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;전체 아키텍처 적용&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;단계적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. 이 모든 걸 한꺼번에 적용해야 하나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아닙니다. &lt;strong&gt;초급편부터 순서대로&lt;/strong&gt; 적용하세요. 초급의 습관만으로도 50% 이상 절약되고, 중급 설정까지 적용하면 70% 이상 줄어듭니다. 고급 전략은 비용이 여전히 높거나 팀 단위로 관리가 필요할 때 도입하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Memory가 너무 많아지면 오히려 토큰이 늘어나지 않나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;네, 그렇습니다. MEMORY.md 인덱스는 매 세션 시작 시 로드되므로, 200줄을 넘지 않도록 관리해야 합니다. 오래되거나 불필요한 Memory는 주기적으로 정리하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Max 요금제에서도 이 전략들이 유효한가요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Max 요금제도 시간당/일별 사용량 제한이 있습니다. 토큰을 효율적으로 쓰면 rate limit에 덜 걸리고, 같은 시간에 더 많은 작업을 처리할 수 있습니다. 속도와 생산성 측면에서 여전히 중요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;시리즈 마무리&lt;/h2&gt;
&lt;p&gt;3편에 걸쳐 Claude Code 토큰 절약 전략을 정리했습니다.&lt;/p&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;&lt;strong&gt;초급&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;습관 (compact, clear, 프롬프트)&lt;/td&gt;
&lt;td&gt;Claude Code 입문자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;중급&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;설정 (claudeignore, Hook, 모델 전환)&lt;/td&gt;
&lt;td&gt;일정 기간 사용한 유저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;고급&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;시스템 (Memory, Skill, 워크트리, CI)&lt;/td&gt;
&lt;td&gt;파워유저 &amp;#x26; 팀 리더&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;결국 핵심은 하나입니다: &lt;strong&gt;Claude에게 보내는 컨텍스트를 최소한으로 유지하면서, 필요한 정보만 정확히 전달하는 것.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 원칙만 기억하면, 새로운 기능이 추가되더라도 토큰 절약 전략을 스스로 세울 수 있습니다.&lt;/p&gt;
&lt;p&gt;여러분만의 토큰 절약 노하우가 있다면 댓글로 공유해주세요! 시리즈를 읽어주셔서 감사합니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude Code 토큰 절약 가이드 [중급편] — 설정과 워크플로우 최적화 7가지]]></title><description><![CDATA[Claude Code 토큰 절약 가이드 중급편 커버 — 설정·워크플로우 최적화 7가지 안녕하세요, 자바파커입니다. "compact도 하고, clear…]]></description><link>https://blog.javapark.kr/token-saving-intermediate/</link><guid isPermaLink="false">https://blog.javapark.kr/token-saving-intermediate/</guid><pubDate>Sun, 05 Apr 2026 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-token-saving-intermediate.png&quot; alt=&quot;Claude Code 토큰 절약 가이드 중급편 커버 — 설정·워크플로우 최적화 7가지&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;compact도 하고, clear도 하고, 프롬프트도 짧게 쓰는데... 그래도 토큰이 빠르게 줄어요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;초급편의 기본 습관을 적용하고 나면, 다음으로 눈을 돌려야 할 곳은 &lt;strong&gt;설정과 워크플로우&lt;/strong&gt;입니다. Claude Code가 불필요하게 읽는 파일을 차단하고, 작업 구조 자체를 최적화하는 단계입니다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;strong&gt;[초급 → 중급 → 고급] 시리즈의 두 번째 편&lt;/strong&gt;입니다. 초급편을 아직 안 읽으셨다면 먼저 확인해보세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. .claudeignore로 불필요한 파일 차단하기&lt;/h2&gt;
&lt;p&gt;Claude Code는 프로젝트를 분석할 때 파일을 읽습니다. 문제는 &lt;strong&gt;읽을 필요 없는 파일까지 읽는다&lt;/strong&gt;는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt;, 빌드 산출물, 로그 파일... 이런 것들이 컨텍스트에 포함되면 토큰이 낭비됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt;처럼 &lt;code class=&quot;language-text&quot;&gt;.claudeignore&lt;/code&gt; 파일을 프로젝트 루트에 만들면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;gitignore&quot;&gt;&lt;pre class=&quot;language-gitignore&quot;&gt;&lt;code class=&quot;language-gitignore&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .claudeignore — Claude Code가 무시할 파일/폴더&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 빌드 산출물&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;dist&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;build&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.next&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;out&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 의존성&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;node_modules&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;vendor&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.venv&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 로그 &amp;amp; 캐시&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.log&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.cache&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.turbo&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;coverage&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 대용량 데이터&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.csv&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.sql&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;data&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 미디어 파일 (읽어도 의미 없음)&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.mp4&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.mp3&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.zip&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 락 파일 (보통 수정할 일 없음)&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;package-lock.json&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;yarn.lock&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;pnpm-lock.yaml&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;.claudeignore의 효과&lt;/h3&gt;
&lt;p&gt;실제 프로젝트에서 측정한 결과입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;프로젝트 유형&lt;/th&gt;
&lt;th&gt;.claudeignore 전&lt;/th&gt;
&lt;th&gt;.claudeignore 후&lt;/th&gt;
&lt;th&gt;절약률&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React SPA (중형)&lt;/td&gt;
&lt;td&gt;파일 탐색 시 ~3,000 파일&lt;/td&gt;
&lt;td&gt;~200 파일&lt;/td&gt;
&lt;td&gt;93% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js API&lt;/td&gt;
&lt;td&gt;~1,500 파일&lt;/td&gt;
&lt;td&gt;~150 파일&lt;/td&gt;
&lt;td&gt;90% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모노레포&lt;/td&gt;
&lt;td&gt;~10,000 파일&lt;/td&gt;
&lt;td&gt;~500 파일&lt;/td&gt;
&lt;td&gt;95% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;파일 탐색 범위가 줄어들면 Claude가 관련 파일을 찾는 과정에서 소모하는 토큰도 크게 줄어듭니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 서브에이전트로 컨텍스트 분리하기&lt;/h2&gt;
&lt;p&gt;Claude Code에는 &lt;strong&gt;서브에이전트(Subagent)&lt;/strong&gt; 기능이 있습니다. 메인 대화의 컨텍스트를 오염시키지 않고 별도의 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;서브에이전트가 토큰을 줄이는 원리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[메인 대화] ────────────────────────────────
  │
  ├─→ [서브에이전트 1] 파일 검색 → 결과만 반환
  │     (자체 컨텍스트에서 작업, 메인에 영향 없음)
  │
  ├─→ [서브에이전트 2] 테스트 실행 → 결과만 반환
  │
  └─→ 메인 대화는 결과 요약만 받음 → 컨텍스트 최소&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;서브에이전트는 &lt;strong&gt;자체 컨텍스트&lt;/strong&gt;에서 작업하고, 메인 대화에는 결과 요약만 돌아옵니다. 대규모 코드 탐색이나 테스트 실행처럼 &lt;strong&gt;출력이 긴 작업&lt;/strong&gt;에서 특히 효과적입니다.&lt;/p&gt;
&lt;h3&gt;서브에이전트를 유도하는 프롬프트&lt;/h3&gt;
&lt;p&gt;Claude Code는 복잡한 작업에서 자동으로 서브에이전트를 사용하지만, 명시적으로 유도할 수도 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;이 프로젝트에서 사용하지 않는 의존성을 찾아줘.
각 의존성별로 사용 여부를 별도로 조사해서 결과만 알려줘.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&quot;별도로 조사해서 결과만&quot;이라는 표현이 서브에이전트 활용을 유도합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 모델 전환으로 토큰 단가 낮추기&lt;/h2&gt;
&lt;p&gt;모든 작업에 최상위 모델을 쓸 필요는 없습니다. Claude Code에서는 작업 중간에 모델을 전환할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;모델별 특성과 추천 용도&lt;/h3&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;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;&lt;strong&gt;Haiku&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매우 빠름&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;가장 저렴&lt;/td&gt;
&lt;td&gt;단순 수정, 포매팅, 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sonnet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;td&gt;중상&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;일반 코딩, 리팩토링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Opus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;최상&lt;/td&gt;
&lt;td&gt;가장 비쌈&lt;/td&gt;
&lt;td&gt;아키텍처 설계, 복잡한 버그&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;실전 전환 전략&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 기본 모델 설정 (설정에서)&lt;/span&gt;
claude config &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; model claude-sonnet-4-6

&lt;span class=&quot;token comment&quot;&gt;# 복잡한 작업이 필요할 때만 Opus로 전환&lt;/span&gt;
claude &lt;span class=&quot;token parameter variable&quot;&gt;--model&lt;/span&gt; claude-opus-4-6&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또는 대화 중에 &lt;code class=&quot;language-text&quot;&gt;/model&lt;/code&gt; 명령어로 전환할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;전략:&lt;/strong&gt; 일상 작업은 Sonnet, 복잡한 설계나 디버깅에만 Opus를 사용하면 비용 효율이 크게 개선됩니다. 단순 포매팅이나 이름 변경 같은 작업은 Haiku로도 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. autocompact 설정하기&lt;/h2&gt;
&lt;p&gt;초급편에서 &lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;를 수동으로 실행하는 법을 다뤘습니다. 이걸 자동화할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ~/.claude/settings.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;preferences&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;autoCompact&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;autocompact 동작 방식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컨텍스트 크기가 임계점을 넘으면 &lt;strong&gt;자동으로 대화를 압축&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용자가 신경 쓰지 않아도 토큰이 관리됨&lt;/li&gt;
&lt;li&gt;압축 시점은 Claude가 자동 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;커스텀 압축 프롬프트&lt;/h3&gt;
&lt;p&gt;단순 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; 대신 문자열을 넣으면 압축 시 지시사항을 줄 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;preferences&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;autoCompact&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;코드 변경 사항과 결정 사항 위주로 유지하고 나머지는 압축&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 중요한 맥락은 보존하면서 불필요한 대화만 정리됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. Hook으로 반복 작업 자동화하기&lt;/h2&gt;
&lt;p&gt;Hook은 특정 이벤트가 발생할 때 &lt;strong&gt;자동으로 쉘 명령을 실행&lt;/strong&gt;하는 기능입니다. Claude가 직접 명령을 실행하는 것보다 토큰 소모가 적습니다.&lt;/p&gt;
&lt;h3&gt;왜 Hook이 토큰을 줄이는가?&lt;/h3&gt;
&lt;p&gt;Claude가 직접 린터를 실행하면:&lt;/p&gt;
&lt;ol&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;Hook으로 자동화하면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;파일 저장 시 자동 실행 (토큰 0)&lt;/li&gt;
&lt;li&gt;에러가 있을 때만 Claude에게 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;토큰 절약에 효과적인 Hook 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ~/.claude/settings.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;PostToolUse&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;matcher&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npx eslint --fix $CLAUDE_FILE_PATH 2&gt;/dev/null || true&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 설정은 Claude가 파일을 수정할 때마다 자동으로 ESLint를 실행합니다. Claude에게 &quot;린트 돌려줘&quot;라고 매번 말할 필요가 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 프롬프트에 출력 형식 제한 걸기&lt;/h2&gt;
&lt;p&gt;Claude의 응답이 길수록 출력 토큰이 늘어납니다. 출력 형식을 제한하면 효과적입니다.&lt;/p&gt;
&lt;h3&gt;답변 길이 제한&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;이 함수의 버그를 찾아서 한 줄로 설명해줘.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;변경이 필요한 파일 목록만 알려줘. 코드는 아직 수정하지 마.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;단계별 작업 지시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 나쁜 예 — 한 번에 모든 것을 요구
&quot;이 프로젝트 분석하고, 문제점 찾고, 해결책 제안하고, 코드 수정까지 해줘&quot;

# 좋은 예 — 단계별로 나눠서 요청
&quot;이 프로젝트의 성능 문제 3가지만 목록으로 알려줘&quot;
→ (결과 확인 후)
&quot;2번 문제를 수정해줘&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;한 번에 모든 걸 요청하면 Claude가 긴 응답을 생성하고, 그 응답이 다음 턴의 컨텍스트가 되어 토큰이 눈덩이처럼 불어납니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7. 파일 지정으로 탐색 범위 줄이기&lt;/h2&gt;
&lt;p&gt;Claude Code는 관련 파일을 찾기 위해 프로젝트를 탐색합니다. 이 탐색 과정 자체가 토큰을 소모합니다.&lt;/p&gt;
&lt;h3&gt;파일을 직접 지정하면?&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 나쁜 예 — Claude가 파일을 찾아야 함
&quot;로그인 버그 고쳐줘&quot;

# 좋은 예 — 파일을 직접 지정
&quot;src/auth/login.ts의 validateToken 함수에서 만료 체크 로직 수정해줘&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;파일 경로와 함수명을 직접 지정하면 &lt;strong&gt;탐색 과정을 건너뛰므로&lt;/strong&gt; 토큰이 절약됩니다.&lt;/p&gt;
&lt;h3&gt;@파일 참조 활용&lt;/h3&gt;
&lt;p&gt;IDE 확장(VS Code 등)에서는 &lt;code class=&quot;language-text&quot;&gt;@&lt;/code&gt; 기호로 파일을 직접 참조할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@src/auth/login.ts 이 파일의 validateToken 함수 수정해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 Claude가 파일을 검색하는 단계 없이 바로 해당 파일을 읽습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;중급편 요약 — 설정 체크리스트&lt;/h2&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;th&gt;적용 난이도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;.claudeignore 설정&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;서브에이전트 활용&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;모델 전환 전략&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;autocompact 설정&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Hook 자동화&lt;/td&gt;
&lt;td&gt;★★★☆☆&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;출력 형식 제한&lt;/td&gt;
&lt;td&gt;★★★☆☆&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;파일 직접 지정&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;초급편의 습관 + 중급편의 설정을 조합하면, 동일한 작업을 &lt;strong&gt;기존 대비 1/3 수준의 토큰&lt;/strong&gt;으로 처리할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. .claudeignore에 너무 많은 파일을 넣으면 Claude가 필요한 파일을 못 읽는 거 아닌가요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;맞습니다. 핵심은 &quot;절대 수정하지 않을 파일&quot;만 넣는 것입니다. &lt;code class=&quot;language-text&quot;&gt;node_modules&lt;/code&gt;, 빌드 산출물, 미디어 파일처럼 코딩 작업과 관련 없는 것만 넣으세요. 소스 코드는 넣지 마세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Haiku 모델로 코딩해도 괜찮나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순한 작업(변수명 변경, import 추가, 포매팅)은 충분합니다. 하지만 복잡한 로직이나 아키텍처 결정이 필요한 작업에서는 품질 차이가 있습니다. 작업 복잡도에 따라 모델을 전환하는 것이 핵심입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. autocompact를 켜면 중요한 대화 내용이 사라지나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude가 자동으로 요약하기 때문에 세부 디테일은 줄어들 수 있습니다. 하지만 코드 변경 사항은 파일에 남아있고, 핵심 결정 사항은 요약에 포함됩니다. 커스텀 프롬프트를 설정하면 어떤 내용을 유지할지 지시할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 고급편에서는 &lt;strong&gt;Memory 시스템, Skill 최적화, 워크트리 격리, 비용 모니터링 자동화&lt;/strong&gt; 등 아키텍처 레벨의 토큰 관리 전략을 다룹니다.&lt;/p&gt;
&lt;p&gt;여러분은 어떤 설정이 가장 효과적이었나요? 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude Code 토큰 절약 가이드 [초급편] — 지금 바로 줄이는 5가지 방법]]></title><description><![CDATA[Claude Code 토큰 절약 가이드 초급편 커버 — compact·clear·CLAUDE.md 5가지 방법 안녕하세요, 자바파커입니다. "Claude Code…]]></description><link>https://blog.javapark.kr/token-saving-beginner/</link><guid isPermaLink="false">https://blog.javapark.kr/token-saving-beginner/</guid><pubDate>Sun, 05 Apr 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-token-saving-beginner.png&quot; alt=&quot;Claude Code 토큰 절약 가이드 초급편 커버 — compact·clear·CLAUDE.md 5가지 방법&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Claude Code 쓰다가 요금 폭탄 맞았어요...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이런 이야기, 한두 번 들은 게 아닙니다. 저도 처음에는 대화 몇 번 했을 뿐인데 토큰이 순식간에 사라져서 당황했습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;기본적인 습관 몇 가지만 들이면 토큰 소모량을 절반 이하로 줄일 수 있습니다.&lt;/strong&gt; 오늘은 Claude Code를 막 시작한 분들도 바로 적용할 수 있는 토큰 절약법 5가지를 정리합니다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;strong&gt;[초급 → 중급 → 고급] 시리즈의 첫 번째 편&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;먼저 알아야 할 것 — 토큰이란?&lt;/h2&gt;
&lt;p&gt;토큰은 AI가 텍스트를 처리하는 최소 단위입니다. 한국어 기준으로 대략 &lt;strong&gt;한 글자가 1~2토큰&lt;/strong&gt;, 영어는 &lt;strong&gt;한 단어가 1토큰&lt;/strong&gt; 정도입니다.&lt;/p&gt;
&lt;p&gt;Claude Code에서 토큰이 소모되는 구조를 정리하면:&lt;/p&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;&lt;strong&gt;입력 토큰&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;내가 보낸 메시지 + 컨텍스트(파일, 대화 기록)&lt;/td&gt;
&lt;td&gt;대화가 길어질수록 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;출력 토큰&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude가 생성한 응답&lt;/td&gt;
&lt;td&gt;응답이 길수록 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;컨텍스트 토큰&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매 턴마다 누적되는 전체 대화 내용&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;가장 큰 비중&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심은 &lt;strong&gt;컨텍스트 토큰&lt;/strong&gt;입니다. Claude Code는 매번 대화 전체를 다시 읽기 때문에, 대화가 길어질수록 &lt;strong&gt;같은 질문이라도 토큰 소모가 기하급수적으로 늘어납니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;비유하자면, 매번 대화할 때마다 &lt;strong&gt;지금까지의 회의록을 처음부터 끝까지 다시 읽는 것&lt;/strong&gt;과 같습니다. 회의록이 10페이지면 10페이지, 100페이지면 100페이지를 매번 읽습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;현재 토큰 사용량 확인하는 법&lt;/h2&gt;
&lt;p&gt;절약하려면 먼저 얼마나 쓰고 있는지 알아야겠죠? Claude Code에서 토큰 사용량을 확인하는 방법은 간단합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;방법 1: 대화 중 확인&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code 하단 상태바에 현재 세션의 토큰 사용량이 표시됩니다. &lt;code class=&quot;language-text&quot;&gt;↑&lt;/code&gt;는 입력 토큰, &lt;code class=&quot;language-text&quot;&gt;↓&lt;/code&gt;는 출력 토큰입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;방법 2: /cost 명령어&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/cost&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재 세션의 누적 비용과 토큰 사용량을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;방법 3: 대시보드에서 확인&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://console.anthropic.com/&quot;&gt;Anthropic Console&lt;/a&gt;에서 API 사용량을 확인할 수 있습니다. Max 요금제 사용자는 &lt;a href=&quot;https://claude.ai/usage&quot;&gt;Claude 사용량 페이지&lt;/a&gt;에서 확인 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;토큰 절약법 1 — /compact로 대화 압축하기&lt;/h2&gt;
&lt;p&gt;가장 즉각적이고 효과적인 방법입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 명령어를 입력하면 Claude가 &lt;strong&gt;지금까지의 대화를 요약&lt;/strong&gt;해서 컨텍스트를 줄여줍니다. 10,000토큰짜리 대화가 2,000~3,000토큰으로 압축됩니다.&lt;/p&gt;
&lt;h3&gt;언제 쓰면 좋을까?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;대화가 20턴 이상 길어졌을 때&lt;/li&gt;
&lt;li&gt;주제가 바뀌었을 때&lt;/li&gt;
&lt;li&gt;상태바의 토큰 수가 급격히 늘었을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;커스텀 압축도 가능&lt;/h3&gt;
&lt;p&gt;특정 내용을 유지하면서 압축하고 싶다면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/compact 파일 수정 내역은 유지하고 나머지 압축해줘&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;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ~/.claude/settings.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;preferences&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;autoCompact&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 설정하면 컨텍스트가 일정 크기를 넘으면 자동으로 압축됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;토큰 절약법 2 — /clear로 깨끗하게 시작하기&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/clear&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;작업 주제가 완전히 바뀔 때는 압축보다 &lt;strong&gt;초기화&lt;/strong&gt;가 낫습니다. &lt;code class=&quot;language-text&quot;&gt;/clear&lt;/code&gt;는 대화 기록을 완전히 지우고 새로 시작합니다.&lt;/p&gt;
&lt;h3&gt;/compact vs /clear 비교&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;/compact&lt;/th&gt;
&lt;th&gt;/clear&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;동작&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대화 요약 후 유지&lt;/td&gt;
&lt;td&gt;대화 기록 완전 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;이전 맥락&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;요약본으로 유지&lt;/td&gt;
&lt;td&gt;완전히 사라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;사용 시점&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;같은 작업 계속할 때&lt;/td&gt;
&lt;td&gt;새 작업 시작할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;토큰 절약&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중간 (60~70% 감소)&lt;/td&gt;
&lt;td&gt;최대 (100% 초기화)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;팁:&lt;/strong&gt; 하나의 작업이 끝나면 습관적으로 &lt;code class=&quot;language-text&quot;&gt;/clear&lt;/code&gt; 후 다음 작업을 시작하세요. 이것만으로도 토큰 낭비를 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;토큰 절약법 3 — 프롬프트를 간결하게 쓰기&lt;/h2&gt;
&lt;p&gt;같은 요청이라도 프롬프트 길이에 따라 토큰 소모가 달라집니다.&lt;/p&gt;
&lt;h3&gt;나쁜 예 (불필요하게 긴 프롬프트)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;안녕하세요. 저는 지금 React 프로젝트를 하고 있는데요,
src/components 폴더에 있는 Header.tsx 파일을 좀 봐주실 수 있을까요?
거기에 네비게이션 메뉴가 있는데, 메뉴 항목을 추가하고 싶습니다.
Home, About, Contact 세 개가 있는데 Blog를 추가해주세요.
스타일은 기존과 동일하게 맞춰주시면 감사하겠습니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;좋은 예 (핵심만 담은 프롬프트)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Header.tsx 네비게이션에 Blog 메뉴 항목 추가해줘. 기존 스타일 유지.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;두 프롬프트의 결과는 동일하지만, 토큰 소모량은 5배 이상 차이&lt;/strong&gt;납니다. Claude Code는 코드베이스를 직접 읽기 때문에 파일 경로나 구조를 장황하게 설명할 필요가 없습니다.&lt;/p&gt;
&lt;h3&gt;간결한 프롬프트 작성 원칙&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인사 생략&lt;/strong&gt; — &quot;안녕하세요&quot; 같은 인사는 토큰 낭비입니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배경 설명 최소화&lt;/strong&gt; — Claude가 코드에서 파악할 수 있는 내용은 생략&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;핵심 동작만 명시&lt;/strong&gt; — &quot;무엇을&quot; &quot;어디에&quot; &quot;어떻게&quot; 세 가지만&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한 번에 하나&lt;/strong&gt; — 여러 작업을 한 프롬프트에 넣으면 응답도 길어짐&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;토큰 절약법 4 — CLAUDE.md로 반복 설명 줄이기&lt;/h2&gt;
&lt;p&gt;매번 같은 규칙을 설명하고 계신가요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 매번 이렇게 말하고 있다면...
&quot;TypeScript로 작성해줘. strict 모드로.
 함수는 화살표 함수로.
 테스트는 vitest로.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 반복 지시를 &lt;strong&gt;CLAUDE.md 파일에 한 번만 적어두면&lt;/strong&gt;, 매 대화에서 자동으로 적용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 프로젝트 루트에 CLAUDE.md 생성 --&gt;&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 코딩 규칙&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; TypeScript strict 모드 사용
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 함수는 화살표 함수로 작성
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 테스트 프레임워크: vitest
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 에러 핸들링: try-catch 필수&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CLAUDE.md는 프로젝트를 열 때 자동으로 읽히기 때문에, 매번 같은 지시를 타이핑하는 토큰을 절약할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;CLAUDE.md도 토큰을 소모한다&lt;/h3&gt;
&lt;p&gt;주의할 점이 있습니다. CLAUDE.md 자체도 매 턴마다 컨텍스트에 포함되므로, &lt;strong&gt;너무 길게 쓰면 오히려 역효과&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CLAUDE.md 길이&lt;/th&gt;
&lt;th&gt;권장 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10줄 이내&lt;/td&gt;
&lt;td&gt;적극 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30줄 이내&lt;/td&gt;
&lt;td&gt;괜찮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100줄 이상&lt;/td&gt;
&lt;td&gt;비효율 — 분리 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;팁:&lt;/strong&gt; 핵심 규칙만 간결하게 적으세요. 길어지면 중급편에서 다룰 Skill이나 Memory로 분리하는 것이 좋습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;토큰 절약법 5 — 작업 단위를 쪼개기&lt;/h2&gt;
&lt;p&gt;가장 많은 사람이 놓치는 부분입니다.&lt;/p&gt;
&lt;h3&gt;나쁜 패턴: 하나의 대화에서 모든 것을 처리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;로그인 기능 만들어줘&quot;
→ (50턴 대화)
→ &quot;그리고 회원가입도 추가해줘&quot;
→ (30턴 추가)
→ &quot;마이페이지도...&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이러면 마지막 요청 시점에는 &lt;strong&gt;80턴 분량의 컨텍스트를 매 턴마다 다시 읽게&lt;/strong&gt; 됩니다.&lt;/p&gt;
&lt;h3&gt;좋은 패턴: 작업별로 새 세션&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[세션 1] &quot;로그인 기능 만들어줘&quot; → 완료 → /clear
[세션 2] &quot;회원가입 기능 추가해줘&quot; → 완료 → /clear
[세션 3] &quot;마이페이지 만들어줘&quot; → 완료&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;작업이 독립적이라면 반드시 세션을 나누세요.&lt;/strong&gt; 각 세션이 짧을수록 토큰 효율이 높아집니다.&lt;/p&gt;
&lt;h3&gt;이상적인 세션 길이&lt;/h3&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;1~10턴&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&gt;
&lt;td&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10~30턴&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;중간에 /compact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30턴 이상&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;/clear 후 재시작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;초급편 요약 — 체크리스트&lt;/h2&gt;
&lt;p&gt;지금 바로 적용할 수 있는 토큰 절약 체크리스트입니다.&lt;/p&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;th&gt;난이도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;로 대화 압축&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/clear&lt;/code&gt;로 세션 초기화&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;프롬프트 간결하게 쓰기&lt;/td&gt;
&lt;td&gt;★★★☆☆&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;CLAUDE.md로 반복 지시 제거&lt;/td&gt;
&lt;td&gt;★★★★☆&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;작업 단위 쪼개기&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 다섯 가지만 습관화해도 &lt;strong&gt;체감 비용이 절반 이하로&lt;/strong&gt; 줄어드는 걸 느끼실 겁니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. /compact를 너무 자주 하면 Claude가 이전 작업을 까먹나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;네, 압축 과정에서 세부 디테일은 사라질 수 있습니다. 중요한 결정 사항이 있다면 &lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt; 전에 CLAUDE.md나 코드 주석으로 남겨두는 게 안전합니다. 하지만 코드에 이미 반영된 변경 사항은 파일을 다시 읽으면 되므로 크게 걱정하지 않아도 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Claude Code에서 토큰을 가장 많이 잡아먹는 건 뭔가요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;컨텍스트 누적&lt;/strong&gt;입니다. 대화가 길어질수록 매 턴마다 이전 대화 전체를 다시 읽기 때문에, 50턴 대화의 마지막 메시지는 첫 메시지보다 수십 배의 토큰을 소모합니다. 세션을 짧게 유지하는 것이 가장 효과적인 절약법입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Max 요금제를 쓰면 토큰 절약이 의미 없나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Max 요금제도 사용량 제한이 있습니다. 토큰을 아끼면 같은 요금으로 더 많은 작업을 할 수 있고, 속도 제한(rate limit)에 덜 걸립니다. 절약 습관은 어떤 요금제든 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 편에서는 &lt;code class=&quot;language-text&quot;&gt;.claudeignore&lt;/code&gt;, 서브에이전트, 모델 선택 등 &lt;strong&gt;설정과 워크플로우 차원의 토큰 최적화&lt;/strong&gt;를 다룹니다.&lt;/p&gt;
&lt;p&gt;궁금한 점이나 여러분만의 토큰 절약 팁이 있다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[하네스 엔지니어링이란? — AI 에이전트를 제어하는 구조적 시스템 설계]]></title><description><![CDATA[안녕하세요, 자바파커입니다. AI 에이전트에게 코드를 맡겼더니, 처음엔 잘 하다가 대화가 길어지니 엉뚱한 코드를 쏟아내기 시작합니다. 비슷한 경험, 한 번쯤 해보셨죠? 프롬프트를 아무리 정교하게 써도 AI가 말을 안 듣는 순간이 옵니다. 최근 AI…]]></description><link>https://blog.javapark.kr/harness-engineering/</link><guid isPermaLink="false">https://blog.javapark.kr/harness-engineering/</guid><pubDate>Sun, 05 Apr 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI 에이전트에게 코드를 맡겼더니, 처음엔 잘 하다가 대화가 길어지니 엉뚱한 코드를 쏟아내기 시작합니다. 비슷한 경험, 한 번쯤 해보셨죠?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;프롬프트를 아무리 정교하게 써도 AI가 말을 안 듣는 순간이 옵니다. 최근 AI 업계에서는 이 문제를 해결하기 위한 새로운 개념이 주목받고 있습니다. 바로 **하네스 엔지니어링(Harness Engineering)**입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — 프롬프트가 AI에게 하는 &apos;부탁&apos;이라면, 하네스는 규칙을 어길 수 없게 만드는 **&apos;강제적 시스템&apos;**입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;하네스 엔지니어링이란?&lt;/h2&gt;
&lt;p&gt;**하네스(Harness)**는 원래 야생말을 통제하기 위한 &apos;마구(고삐, 안장)&apos;를 뜻합니다. 이 비유가 AI에도 그대로 적용됩니다.&lt;/p&gt;
&lt;p&gt;거대언어모델(LLM)이라는 강력한 야생말이 있습니다. 엄청난 능력을 가졌지만, 고삐 없이 달리게 놔두면 어디로 튈지 모릅니다. &lt;strong&gt;하네스 엔지니어링&lt;/strong&gt;은 이 야생말이 인간의 의도대로 정확하게 달릴 수 있도록 제어하고 가이드하는 &lt;strong&gt;구조적 시스템을 설계&lt;/strong&gt;하는 것입니다.&lt;/p&gt;
&lt;h3&gt;프롬프트 엔지니어링과 뭐가 다른가?&lt;/h3&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;방식&lt;/td&gt;
&lt;td&gt;AI에게 &apos;부탁&apos;&lt;/td&gt;
&lt;td&gt;AI를 &apos;강제&apos;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제어 수준&lt;/td&gt;
&lt;td&gt;요청 수준&lt;/td&gt;
&lt;td&gt;시스템 수준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;위반 시&lt;/td&gt;
&lt;td&gt;AI가 무시할 수 있음&lt;/td&gt;
&lt;td&gt;물리적으로 불가능하게 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비유&lt;/td&gt;
&lt;td&gt;&quot;천천히 달려줘&quot;&lt;/td&gt;
&lt;td&gt;속도 제한 장치를 달아버림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;프롬프트에 &quot;절대 DB를 삭제하지 마&quot;라고 써도, AI가 맥락을 잃으면 삭제 명령을 실행할 수 있습니다. 하네스는 애초에 DB 삭제 명령이 실행되지 못하도록 시스템 레벨에서 차단합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 하네스가 필요한가?&lt;/h2&gt;
&lt;p&gt;AI 에이전트를 실무에 투입해보면, 두 가지 고질적 문제를 마주하게 됩니다.&lt;/p&gt;
&lt;h3&gt;문제 1: 컨텍스트 부패 (Context Rot)&lt;/h3&gt;
&lt;p&gt;대화가 길어질수록 AI가 앞선 내용을 잊어버리거나 엉뚱한 결론을 내리는 현상입니다. 처음에 &quot;TypeScript로 작성해&quot;라고 했는데, 대화 50턴쯤 되면 슬그머니 JavaScript로 코드를 쓰기 시작합니다.&lt;/p&gt;
&lt;p&gt;컨텍스트 윈도우에는 한계가 있고, 대화가 쌓일수록 초기 지시사항의 영향력은 약해집니다. 프롬프트만으로는 이 문제를 근본적으로 해결할 수 없습니다.&lt;/p&gt;
&lt;h3&gt;문제 2: 규칙 및 울타리 문제&lt;/h3&gt;
&lt;p&gt;AI가 정보는 알고 있지만, 하면 안 되는 행동을 제어할 물리적 장치가 없는 문제입니다.&lt;/p&gt;
&lt;p&gt;예를 들어:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로덕션 DB에 직접 쿼리를 날리는 것&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; 브랜치에 강제 push하는 것&lt;/li&gt;
&lt;li&gt;민감한 설정 파일을 수정하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI는 &quot;이러면 안 된다&quot;는 것을 &apos;알고&apos; 있어도, 시스템적 제약이 없으면 실행할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;하네스를 구성하는 3대 기둥&lt;/h2&gt;
&lt;p&gt;하네스 엔지니어링은 세 가지 핵심 요소로 구성됩니다.&lt;/p&gt;
&lt;h3&gt;기둥 1: 컨텍스트 파일 (Context Files)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;agent.md&lt;/code&gt; 같은 &lt;strong&gt;온보딩 문서&lt;/strong&gt;입니다. AI가 새로운 세션을 시작할 때마다 가장 먼저 읽게 하여, 프로젝트의 규칙과 절대 해서는 안 될 일을 상기시킵니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; CLAUDE.md 예시&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 절대 규칙&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 프로덕션 DB에 직접 쿼리 금지
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; main 브랜치에 직접 push 금지
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; .env 파일 수정 금지

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 코딩 규칙&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; TypeScript strict 모드 사용
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 테스트 없는 코드 커밋 금지
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 기존 함수 시그니처 변경 시 반드시 확인 요청&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사람으로 치면 신입 사원에게 주는 &lt;strong&gt;온보딩 가이드&lt;/strong&gt;와 같습니다. 매 세션마다 &quot;우리 팀은 이렇게 일합니다&quot;를 읽게 하는 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 포인트:&lt;/strong&gt; 컨텍스트 파일은 대화가 아무리 길어져도 항상 최우선으로 참조됩니다. 프롬프트는 묻힐 수 있지만, 시스템에 등록된 컨텍스트 파일은 묻히지 않습니다.&lt;/p&gt;
&lt;h3&gt;기둥 2: 자동 강제 시스템 (Automated Enforcement)&lt;/h3&gt;
&lt;p&gt;AI가 코드를 저장하거나 커밋하려 할 때 자동으로 규칙을 체크하는 장치입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;린터(Linter)와 훅(Hook):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// .claude/settings.json 훅 예시&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;matcher&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm run lint --fix&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AI가 파일을 수정할 때마다 린터가 자동으로 돌아갑니다. 규칙에 어긋나면 저장 자체가 실패합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;자동 교정 루프:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여기서 핵심은 오류가 발견되었을 때 사람이 개입하는 대신, &lt;strong&gt;시스템이 AI에게 다시 수정을 명령&lt;/strong&gt;한다는 점입니다. AI가 잘못된 코드를 쓰면 → 린터가 감지 → AI에게 수정 지시 → AI가 스스로 고침. 이 루프가 사람 개입 없이 돌아갑니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&quot;성공은 조용히, 실패는 시끄럽게.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;잘 동작할 때는 아무 일도 일어나지 않고, 문제가 생겼을 때만 시스템이 개입합니다. 이것이 좋은 하네스 설계의 원칙입니다.&lt;/p&gt;
&lt;h3&gt;기둥 3: 가비지 컬렉션 (Garbage Collection)&lt;/h3&gt;
&lt;p&gt;프로그래밍에서 가비지 컬렉션이 안 쓰는 메모리를 정리하듯, AI가 만들어낸 &lt;strong&gt;나쁜 코드 패턴이나 안 쓰는 코드를 주기적으로 청소&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;p&gt;왜 이게 중요할까요? AI는 기존 코드베이스를 참고해서 새 코드를 작성합니다. 만약 이전에 AI가 만든 나쁜 패턴이 그대로 남아있으면, AI가 그것을 &apos;정상적인 코드&apos;로 학습하고 같은 실수를 반복합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;나쁜 코드 잔존 → AI가 참고 → 비슷한 나쁜 코드 생성 → 나쁜 코드 더 쌓임 → ...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 악순환을 끊으려면 주기적으로 코드를 정리해서 AI가 참고할 코드베이스의 품질을 유지해야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시: 프롬프트 vs 하네스&lt;/h2&gt;
&lt;p&gt;같은 문제를 프롬프트와 하네스로 각각 해결하는 방식을 비교해보겠습니다.&lt;/p&gt;
&lt;h3&gt;상황: AI가 테스트 없이 코드를 커밋하려 한다&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;프롬프트 방식:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;코드를 작성한 후 반드시 테스트를 작성하고, 테스트가 통과한 후에만 커밋하세요.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;→ 대화가 길어지면 잊어버리고 테스트 없이 커밋할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하네스 방식:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# pre-commit 훅&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;테스트 실패: 커밋이 차단되었습니다&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;→ 테스트가 실패하면 물리적으로 커밋이 불가능&lt;/p&gt;
&lt;h3&gt;상황: AI가 프로덕션 환경 설정을 건드리려 한다&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;프롬프트 방식:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;절대로 .env.production 파일을 수정하지 마세요.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;→ 맥락 혼동 시 수정할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하네스 방식:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;matcher&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node scripts/block-production-files.js&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;→ 시스템이 차단하므로 AI의 의도와 무관하게 수정 불가&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;개발자 역할의 변화: &apos;선수&apos;에서 &apos;감독&apos;으로&lt;/h2&gt;
&lt;p&gt;하네스 엔지니어링이 부상하면서 개발자의 역할도 변하고 있습니다. 이전에는 코드 한 줄 한 줄을 직접 짜는 &lt;strong&gt;엄밀함&lt;/strong&gt;이 중요했다면, 이제는 AI가 올바르게 동작할 수 있는 환경(시스템)을 설계하는 &lt;strong&gt;엄밀함&lt;/strong&gt;이 요구됩니다.&lt;/p&gt;
&lt;p&gt;이것을 **&apos;엄밀함의 재배치&apos;**라고 표현할 수 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;코드를 직접 작성&lt;/td&gt;
&lt;td&gt;AI가 작성할 규칙을 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;버그를 직접 수정&lt;/td&gt;
&lt;td&gt;버그를 방지하는 시스템 구축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 리뷰&lt;/td&gt;
&lt;td&gt;자동 검증 파이프라인 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개별 기능 구현&lt;/td&gt;
&lt;td&gt;AI 워크플로우 아키텍처 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;기술적 수준이 낮아지는 것이 아닙니다. 오히려 &lt;strong&gt;더 높은 차원의 설계 능력&lt;/strong&gt;이 필요합니다. 축구로 비유하면, 훌륭한 선수에서 훌륭한 감독이 되는 것과 같습니다.&lt;/p&gt;
&lt;p&gt;솔직히 말하면, OpenAI는 코드 한 줄 쓰지 않고 시스템 개선만으로 AI 에이전트의 성능을 크게 올렸다는 사례도 있습니다. 코드를 잘 짜는 능력보다 &lt;strong&gt;시스템을 잘 설계하는 능력&lt;/strong&gt;이 더 큰 레버리지를 만들어내는 시대가 오고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;지금 바로 시작할 수 있는 것들&lt;/h2&gt;
&lt;p&gt;하네스 엔지니어링이 거창하게 느껴질 수 있지만, 당장 적용할 수 있는 것들이 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 작성하기&lt;/strong&gt; — 프로젝트 루트에 AI를 위한 온보딩 문서를 만들어보세요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pre-commit 훅 설정&lt;/strong&gt; — 린터, 테스트 자동 실행으로 기본적인 품질 게이트 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코드 정리 루틴&lt;/strong&gt; — 주기적으로 안 쓰는 코드, 나쁜 패턴을 정리하는 습관&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 실수 패턴 기록&lt;/strong&gt; — AI가 반복하는 실수를 발견하면 컨텍스트 파일에 규칙으로 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;하네스 엔지니어링은 프롬프트 엔지니어링을 대체하나요?&lt;/h3&gt;
&lt;p&gt;대체가 아니라 &lt;strong&gt;보완&lt;/strong&gt;입니다. 프롬프트는 AI에게 &apos;무엇을 할지&apos; 알려주는 역할이고, 하네스는 &apos;하면 안 되는 것&apos;을 시스템으로 차단하는 역할입니다. 좋은 프롬프트 + 견고한 하네스 = 신뢰할 수 있는 AI 에이전트입니다.&lt;/p&gt;
&lt;h3&gt;소규모 프로젝트에도 하네스가 필요한가요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 하나만 작성해도 하네스의 시작입니다. 프로젝트 규모와 관계없이, AI와 협업한다면 최소한의 규칙 문서는 만들어두는 것을 추천합니다. 투자 대비 효과가 가장 큰 시작점입니다.&lt;/p&gt;
&lt;h3&gt;Claude Code에서 하네스를 어떻게 적용하나요?&lt;/h3&gt;
&lt;p&gt;Claude Code는 &lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 파일을 자동으로 인식하고, hooks 설정을 통해 자동 강제 시스템을 구성할 수 있습니다. 즉, 하네스 엔지니어링을 가장 자연스럽게 적용할 수 있는 도구 중 하나입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;AI 에이전트 시대에 개발자의 역량은 코드를 잘 짜는 것에서 &lt;strong&gt;시스템을 잘 설계하는 것&lt;/strong&gt;으로 이동하고 있습니다. 하네스 엔지니어링은 그 변화의 핵심 키워드입니다.&lt;/p&gt;
&lt;p&gt;여러분은 AI 에이전트와 협업할 때 어떤 &apos;고삐&apos;를 사용하고 계신가요? 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub Pages에서 Netlify로 전환하기 — 소스 비공개와 커스텀 도메인]]></title><description><![CDATA[안녕하세요, 자바파커입니다. GitHub Pages로 블로그를 운영하면, 소스 코드가 전부 공개됩니다. 글은 공개해도 괜찮은데, 초안이나 설정 파일까지 공개되는 건 좀 꺼림칙하지 않으신가요? 이번 포스팅에서는 GitHub Pages에서 Netlify…]]></description><link>https://blog.javapark.kr/09-netlify-migration/</link><guid isPermaLink="false">https://blog.javapark.kr/09-netlify-migration/</guid><pubDate>Sun, 05 Apr 2026 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GitHub Pages로 블로그를 운영하면, 소스 코드가 전부 공개됩니다. 글은 공개해도 괜찮은데, 초안이나 설정 파일까지 공개되는 건 좀 꺼림칙하지 않으신가요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이번 포스팅에서는 GitHub Pages에서 Netlify로 블로그 배포를 전환한 이유와 과정을 공유합니다. 결론부터 말씀드리면 — 소스 비공개가 필요하다면 Netlify가 정답입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 전환했나?&lt;/h2&gt;
&lt;p&gt;GitHub Pages는 훌륭한 무료 호스팅이지만, 한 가지 제약이 있습니다. &lt;strong&gt;무료 플랜에서는 리포지토리가 반드시 public&lt;/strong&gt;이어야 합니다. 이는 곧 다음을 의미합니다.&lt;/p&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;발행된 글 내용&lt;/td&gt;
&lt;td&gt;어차피 공개 (문제 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마크다운 원본&lt;/td&gt;
&lt;td&gt;의도치 않은 복사 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작성 중인 초안&lt;/td&gt;
&lt;td&gt;미완성 글이 노출됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blog-config.js&lt;/td&gt;
&lt;td&gt;개인 설정 정보 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커밋 히스토리&lt;/td&gt;
&lt;td&gt;삭제한 글도 이력에 남음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;솔직히 개인 블로그라 큰 문제는 아닙니다. 하지만 &lt;strong&gt;Netlify는 private 리포지토리와 무료로 연동&lt;/strong&gt;되기 때문에, 소스는 비공개로 유지하면서 빌드 결과물만 배포할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub Pages vs Netlify 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;GitHub Pages&lt;/th&gt;
&lt;th&gt;Netlify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;가격&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료 (월 300분 빌드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private 리포지토리&lt;/td&gt;
&lt;td&gt;유료 플랜 필요&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;무료 지원&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;글로벌 CDN&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드 속도&lt;/td&gt;
&lt;td&gt;Actions 경유 (느림)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;직접 빌드 (빠름)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR 미리보기&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PR별 미리보기 URL&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커스텀 도메인&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리다이렉트&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;_redirects&lt;/code&gt; 지원&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버리스 함수&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;지원&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;폼 처리&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;내장 지원&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대역폭&lt;/td&gt;
&lt;td&gt;100GB/월&lt;/td&gt;
&lt;td&gt;100GB/월&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;개인 블로그 수준에서는 성능 차이가 크지 않지만, &lt;strong&gt;소스 비공개 + PR 미리보기 + 글로벌 CDN&lt;/strong&gt;이 무료로 제공되는 점이 Netlify의 확실한 장점입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;전환 과정&lt;/h2&gt;
&lt;h3&gt;1단계: Netlify 사이트 생성&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netlify.com&quot;&gt;netlify.com&lt;/a&gt; 가입/로그인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add new site&lt;/strong&gt; → &lt;strong&gt;Import an existing project&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;GitHub 연결 → 리포지토리 선택&lt;/li&gt;
&lt;li&gt;빌드 설정 입력:&lt;/li&gt;
&lt;/ol&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;Build command&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish directory&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;public/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;strong&gt;Environment variables&lt;/strong&gt;에 &lt;code class=&quot;language-text&quot;&gt;NODE_VERSION=18&lt;/code&gt; 추가 (Gatsby 5 호환을 위해 필수)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy site&lt;/strong&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2단계: 커스텀 도메인 연결&lt;/h3&gt;
&lt;p&gt;저는 &lt;code class=&quot;language-text&quot;&gt;blog.javapark.kr&lt;/code&gt; 도메인을 사용하기로 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Netlify 대시보드:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Site settings → Domain management&lt;/li&gt;
&lt;li&gt;Add custom domain → &lt;code class=&quot;language-text&quot;&gt;blog.javapark.kr&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;Verify → Add domain&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;DNS 설정 (도메인 관리 업체에서):&lt;/strong&gt;&lt;/p&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;CNAME&lt;/td&gt;
&lt;td&gt;blog&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;[사이트명].netlify.app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3단계: HTTPS 인증서&lt;/h3&gt;
&lt;p&gt;DNS 설정이 전파되면 Netlify가 &lt;strong&gt;Let&apos;s Encrypt 인증서를 자동으로 발급&lt;/strong&gt;합니다. Domain management → HTTPS 섹션에서 상태를 확인할 수 있습니다. 보통 수분에서 수시간 정도 소요됩니다.&lt;/p&gt;
&lt;h3&gt;4단계: Gatsby 설정 변경&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;blog-config.js&lt;/code&gt;에서 siteUrl을 새 도메인으로 변경합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Before&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;siteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://javapark.github.io&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// After&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;siteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://blog.javapark.kr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 설정은 사이트맵, RSS 피드, 프로필 이미지 경로 등에 영향을 주므로 반드시 변경해야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;전환 후 체크리스트&lt;/h2&gt;
&lt;p&gt;Netlify 배포가 완료된 후 확인해야 할 항목들입니다.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 메인 페이지 정상 렌더링&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 포스트 내 이미지 정상 표시&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 프로필 이미지 표시&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 검색 기능 동작&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; RSS 피드 URL 정상&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 사이트맵 URL 정상&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Giscus 댓글 시스템 동작&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Google Analytics 태그 동작&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; HTTPS 인증서 적용 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub Pages는 어떻게 하나요?&lt;/h2&gt;
&lt;p&gt;기존 GitHub Pages와 Netlify를 동시에 운영할 수도 있습니다. 하지만 같은 소스에서 두 사이트를 운영하면 SEO에 불리하므로 (중복 콘텐츠), 하나를 선택하는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;GitHub Pages를 중단하려면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;리포지토리 Settings → Pages → Source를 &quot;None&quot;으로 변경&lt;/li&gt;
&lt;li&gt;또는 &lt;code class=&quot;language-text&quot;&gt;.github/workflows/gatsby.yml&lt;/code&gt; 워크플로우를 삭제/비활성화&lt;/li&gt;
&lt;li&gt;리포지토리를 Private으로 전환&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Netlify 무료 플랜에 빌드 제한이 있나요?&lt;/h3&gt;
&lt;p&gt;네, 월 300분입니다. Gatsby 블로그 빌드가 보통 2&lt;del&gt;5분 정도이므로, 하루에 2&lt;/del&gt;3번 배포해도 충분합니다. 개인 블로그라면 제한에 걸릴 일은 거의 없습니다.&lt;/p&gt;
&lt;h3&gt;GitHub Pages에서 Netlify로 전환하면 SEO에 영향이 있나요?&lt;/h3&gt;
&lt;p&gt;도메인이 바뀌면 검색 엔진이 새 URL을 다시 크롤링해야 하므로, 일시적으로 검색 순위가 변동될 수 있습니다. 기존 도메인에서 새 도메인으로 301 리다이렉트를 설정하면 영향을 최소화할 수 있습니다. 처음부터 커스텀 도메인을 사용했다면 도메인만 CNAME 대상을 변경하면 되므로 SEO 영향이 없습니다.&lt;/p&gt;
&lt;h3&gt;다시 GitHub Pages로 돌아갈 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, 언제든 가능합니다. Netlify를 해제하고 GitHub Pages를 다시 활성화하면 됩니다. 소스 코드 자체는 동일하므로 전환 비용이 거의 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;시리즈를 마치며&lt;/h2&gt;
&lt;p&gt;이번 시리즈에서는 GitHub 블로그를 운영하면서 마주친 실전 문제들을 다뤘습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/07-fix-actions-deprecated/&quot;&gt;GitHub Actions deprecated 에러 해결&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/08-search-improvement/&quot;&gt;검색 기능 대폭 개선&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/09-netlify-migration/&quot;&gt;GitHub Pages에서 Netlify로 전환&lt;/a&gt; (이번 글)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;블로그를 만드는 것도 중요하지만, &lt;strong&gt;꾸준히 유지보수하고 개선하는 과정&lt;/strong&gt;이 진짜 실력을 키워주는 것 같습니다. 여러분의 블로그는 어떤 플랫폼에서 운영하고 계신가요? 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Gatsby 블로그 검색 기능 개선 — AND/OR 검색, 관련도 정렬까지]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 블로그에 검색 기능이 있긴 한데, 정작 원하는 글을 찾기가 어렵습니다. Gatsby 블로그의 기본 검색은 단순 문자열 매칭이라 태그로 검색도 안 되고, 결과 정렬도 날짜순이라 원하는 글을 찾기 힘들었습니다. Algolia…]]></description><link>https://blog.javapark.kr/08-search-improvement/</link><guid isPermaLink="false">https://blog.javapark.kr/08-search-improvement/</guid><pubDate>Sun, 05 Apr 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;블로그에 검색 기능이 있긴 한데, 정작 원하는 글을 찾기가 어렵습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gatsby 블로그의 기본 검색은 단순 문자열 매칭이라 태그로 검색도 안 되고, 결과 정렬도 날짜순이라 원하는 글을 찾기 힘들었습니다. Algolia 같은 외부 서비스를 쓸 수도 있지만, 개인 블로그에 과한 느낌이 들어서 직접 개선해봤습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기존 검색의 한계&lt;/h2&gt;
&lt;p&gt;gatsby-starter-hoodie 테마의 기본 검색 코드를 보면:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filteredPosts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; title &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frontmatter
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lowerQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rawMarkdownBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;문제점은 명확합니다.&lt;/p&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;태그 검색 불가&lt;/td&gt;
&lt;td&gt;태그가 검색 대상에 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관련도 정렬 없음&lt;/td&gt;
&lt;td&gt;제목 매칭이든 본문 매칭이든 동일 취급, 날짜순 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복합 검색 불가&lt;/td&gt;
&lt;td&gt;여러 키워드로 AND/OR 검색 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피드백 부족&lt;/td&gt;
&lt;td&gt;어디서 매칭됐는지, 결과가 없을 때 안내 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;개선 1: 검색 로직 강화&lt;/h2&gt;
&lt;h3&gt;태그 검색 추가 + 관련도 점수&lt;/h3&gt;
&lt;p&gt;제목, 태그, 본문 각각에 가중치를 부여해서 관련도 순으로 정렬합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;scorePost&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; term&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; score &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; matchType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 제목 매칭 (최우선, 100점 + 완전 일치 보너스 50점)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerTitle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    score &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerTitle &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; score &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;
    matchType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 태그 매칭 (50점)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    score &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;matchType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; matchType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tag&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 본문 매칭 (10점)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowerBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    score &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;matchType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; matchType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;body&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; score&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matchType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;점수 체계를 정리하면:&lt;/p&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;제목 완전 일치&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;가장 정확한 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제목 부분 일치&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;높은 관련도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;태그 일치&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;주제 관련성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;본문 일치&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;참고 수준&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;AND/OR 복합 검색&lt;/h3&gt;
&lt;p&gt;쉼표와 공백으로 검색 모드를 자동 판별합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;parseQuery&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;rawQuery&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trimmed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 쉼표: OR 검색 — 하나라도 포함되면 결과에 표시&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;or&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;terms&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; trimmed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; terms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; trimmed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\s+&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;terms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 공백: AND 검색 — 모든 키워드가 포함된 글만 표시&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;and&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; terms &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;single&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; terms &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사용 예시:&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;단일&lt;/td&gt;
&lt;td&gt;python이 포함된 글&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;python django&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AND&lt;/td&gt;
&lt;td&gt;python과 django 모두 포함된 글&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;python, java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OR&lt;/td&gt;
&lt;td&gt;python 또는 java가 포함된 글&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;AND 모드에서는 하나라도 매칭되지 않으면 결과에서 제외합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parsed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mode &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;and&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; term &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; parsed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;terms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; score&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matchType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scorePost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;score &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;matchType&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 하나라도 안 맞으면 탈락&lt;/span&gt;
    totalScore &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; score
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;디바운스 적용&lt;/h3&gt;
&lt;p&gt;타이핑할 때마다 전체 포스트를 필터링하면 비효율적입니다. 200ms 디바운스를 적용해서 타이핑이 멈춘 후에 검색을 실행합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useDebounce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;debounced&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setDebounced&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setDebounced&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; debounced
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; debouncedQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useDebounce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;개선 2: UI/UX 향상&lt;/h2&gt;
&lt;h3&gt;검색어 하이라이팅&lt;/h3&gt;
&lt;p&gt;검색 결과에서 매칭된 키워드를 노란색으로 강조 표시합니다. AND/OR 검색 시 모든 키워드가 각각 하이라이팅됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;highlightText&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; terms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; escaped &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; terms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;escapeRegExp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;|&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;escaped&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;regex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;part&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    terms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Highlight key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;part&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Highlight&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      part
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;매칭 위치 배지&lt;/h3&gt;
&lt;p&gt;각 검색 결과에 &quot;제목 일치&quot;, &quot;태그 일치&quot;, &quot;본문 일치&quot; 배지를 색상별로 표시해서 왜 이 글이 검색됐는지 한눈에 파악할 수 있습니다.&lt;/p&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;제목 일치&lt;/td&gt;
&lt;td&gt;노란색&lt;/td&gt;
&lt;td&gt;제목에서 키워드를 찾음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;태그 일치&lt;/td&gt;
&lt;td&gt;보라색&lt;/td&gt;
&lt;td&gt;태그에서 키워드를 찾음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;본문 일치&lt;/td&gt;
&lt;td&gt;파란색&lt;/td&gt;
&lt;td&gt;본문에서 키워드를 찾음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;본문 매칭 컨텍스트&lt;/h3&gt;
&lt;p&gt;본문에서 매칭된 경우, 단순 excerpt 대신 &lt;strong&gt;매칭된 부분 주변 텍스트&lt;/strong&gt;를 발췌해서 보여줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getMatchContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; terms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contextLen &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lower &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; bestIdx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; term &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; terms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lower&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;idx &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bestIdx &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; idx &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; bestIdx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; bestIdx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; idx
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// bestIdx 기준으로 앞뒤 80자씩 발췌&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bestIdx &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; contextLen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bestIdx &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; contextLen &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; snippet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; snippet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;...&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; snippet
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; snippet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; snippet &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;...&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; snippet
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;검색 전 추천 섹션&lt;/h3&gt;
&lt;p&gt;검색어를 입력하기 전에는 &lt;strong&gt;인기 태그&lt;/strong&gt;와 &lt;strong&gt;최근 글&lt;/strong&gt;을 보여줍니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인기 태그&lt;/strong&gt;: 사용 빈도 상위 12개를 칩 형태로 표시. 클릭하면 해당 태그로 바로 검색&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;최근 글&lt;/strong&gt;: 최신 5개 포스트를 제목, 카테고리, 날짜와 함께 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; popularTags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMemo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tagCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frontmatter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tags &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      tagCount&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagCount&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;기타 UX 개선&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;초기화 버튼 (X)&lt;/strong&gt;: 검색어를 한 번에 지우고 다시 입력할 수 있는 버튼&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검색 결과 카운트&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;&quot;검색어&quot; 검색 결과 N건 (AND/OR 모드 표시)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빈 결과 안내&lt;/strong&gt;: 결과가 없을 때 &quot;다른 키워드로 검색해 보세요&quot; 안내&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 포커스&lt;/strong&gt;: 검색 페이지 진입 시 검색창에 자동 포커스&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;전후 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;검색 대상&lt;/td&gt;
&lt;td&gt;제목 + 본문&lt;/td&gt;
&lt;td&gt;제목 + 태그 + 본문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정렬&lt;/td&gt;
&lt;td&gt;날짜순&lt;/td&gt;
&lt;td&gt;관련도순&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복합 검색&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;AND(공백) / OR(쉼표)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;하이라이팅&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;키워드 강조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매칭 정보&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;위치 배지 + 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 전 화면&lt;/td&gt;
&lt;td&gt;전체 글 목록&lt;/td&gt;
&lt;td&gt;인기 태그 + 최근 글&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;입력 최적화&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;디바운스 200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;외부 검색 서비스(Algolia 등)와 비교하면 어떤가요?&lt;/h3&gt;
&lt;p&gt;Algolia는 오타 교정(fuzzy matching), 검색어 자동완성, 분석 대시보드 등 강력한 기능을 제공합니다. 하지만 무료 플랜에 제한이 있고, 별도의 인덱싱 설정이 필요합니다. 글이 100개 이하인 개인 블로그라면 클라이언트 사이드 검색으로 충분합니다.&lt;/p&gt;
&lt;h3&gt;글이 많아지면 성능 문제가 생기지 않나요?&lt;/h3&gt;
&lt;p&gt;현재 방식은 모든 글의 rawMarkdownBody를 클라이언트에 로드합니다. 글이 수백 개 이상이 되면 초기 로딩 시간이 길어질 수 있습니다. 그때는 검색 인덱스를 별도로 생성하거나 외부 서비스를 도입하는 것이 좋습니다.&lt;/p&gt;
&lt;h3&gt;AND와 OR를 동시에 사용할 수 있나요?&lt;/h3&gt;
&lt;p&gt;현재는 쉼표가 있으면 OR, 공백만 있으면 AND로 단일 모드만 지원합니다. &lt;code class=&quot;language-text&quot;&gt;(A AND B) OR C&lt;/code&gt; 같은 복합 조건은 지원하지 않습니다. 개인 블로그 검색에서는 이 정도면 충분하다고 판단했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;GitHub Pages에서 Netlify로 전환한 이유와 과정&lt;/strong&gt;을 공유합니다. 소스 코드 비공개, 커스텀 도메인 설정까지 다룹니다.&lt;/p&gt;
&lt;p&gt;검색 기능 관련해서 추가로 개선하면 좋을 아이디어가 있다면 댓글로 알려주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub Actions deprecated 에러 해결 — Gatsby 빌드 실패 대처법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 잘 돌아가던 블로그 빌드가 갑자기 실패했습니다. 코드는 하나도 안 건드렸는데요. GitHub Actions에서 deprecated…]]></description><link>https://blog.javapark.kr/07-fix-actions-deprecated/</link><guid isPermaLink="false">https://blog.javapark.kr/07-fix-actions-deprecated/</guid><pubDate>Sun, 05 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;잘 돌아가던 블로그 빌드가 갑자기 실패했습니다. 코드는 하나도 안 건드렸는데요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub Actions에서 deprecated 버전 에러가 발생하면서 빌드가 멈추는 경험을 하셨다면, 이 포스팅이 도움이 될 겁니다. 저도 같은 상황을 겪고 해결한 과정을 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;문제 상황: 갑자기 빌드가 안 된다&lt;/h2&gt;
&lt;p&gt;GitHub Actions 빌드 로그에 이런 에러가 나타났습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Error: This request has been automatically failed because it uses a deprecated
version of `actions/upload-artifact: v3`.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;코드를 전혀 수정하지 않았는데, GitHub 측에서 &lt;strong&gt;Actions v3 지원을 중단&lt;/strong&gt;하면서 기존 워크플로우가 동작하지 않게 된 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;원인: GitHub Actions v3 지원 종료&lt;/h2&gt;
&lt;p&gt;GitHub는 2024년부터 단계적으로 Actions v3를 deprecated 처리했고, 결국 v3 기반 워크플로우는 자동으로 실패하게 되었습니다. 영향을 받는 주요 액션은 다음과 같습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;deprecated 버전&lt;/th&gt;
&lt;th&gt;최신 버전&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;actions/checkout&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions/setup-node&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions/configure-pages&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions/cache&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions/upload-pages-artifact&lt;/td&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actions/deploy-pages&lt;/td&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;해결 1: Actions 버전 업데이트&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.github/workflows/gatsby.yml&lt;/code&gt; 파일에서 모든 액션 버전을 최신으로 변경합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Before (deprecated)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v3
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v3
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/configure&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v3
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/cache@v3
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;artifact@v2
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v2

&lt;span class=&quot;token comment&quot;&gt;# After (최신)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/configure&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v5
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/cache@v4
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;artifact@v3
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이것만으로 deprecated 에러는 해결됩니다. 하지만 저는 여기서 끝나지 않았습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;해결 2: remark-math 호환성 에러&lt;/h2&gt;
&lt;p&gt;Actions 버전을 올리고 다시 빌드했더니, 이번에는 다른 에러가 나타났습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;TypeError: Cannot set properties of undefined (setting &apos;math&apos;)
  - block.js:203 Function.blockPlugin
    [javapark.github.io]/[remark-math]/block.js:203:24&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;원인을 추적해보니, &lt;code class=&quot;language-text&quot;&gt;gatsby-remark-katex@3.10.0&lt;/code&gt;이 의존하는 &lt;code class=&quot;language-text&quot;&gt;remark-math@1.0.6&lt;/code&gt;이 &lt;code class=&quot;language-text&quot;&gt;gatsby-transformer-remark@6&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;unified@9&lt;/code&gt;와 &lt;strong&gt;호환되지 않는&lt;/strong&gt; 것이었습니다.&lt;/p&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;gatsby-transformer-remark&lt;/td&gt;
&lt;td&gt;6.16.0&lt;/td&gt;
&lt;td&gt;unified@9 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gatsby-remark-katex&lt;/td&gt;
&lt;td&gt;3.10.0&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;mailto:remark-math@1.x&quot;&gt;remark-math@1.x&lt;/a&gt; 의존&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remark-math&lt;/td&gt;
&lt;td&gt;1.0.6&lt;/td&gt;
&lt;td&gt;unified@9와 비호환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;KaTeX 수식 기능을 사용하지 않고 있었기 때문에, 과감히 제거하기로 했습니다.&lt;/p&gt;
&lt;h3&gt;제거 순서&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. gatsby-config.js에서 플러그인 제거&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 아래 블록 삭제&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-remark-katex&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;strict&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ignore&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. package.json에서 패키지 제거&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 아래 두 줄 삭제&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;gatsby-remark-katex&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^3.5.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;katex&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^0.12.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;3. gatsby-browser.js에서 CSS import 제거&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 아래 줄 삭제&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;katex/dist/katex.min.css&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 세 번째 단계를 놓치면 빌드 시 &lt;code class=&quot;language-text&quot;&gt;Can&apos;t resolve &apos;katex/dist/katex.min.css&apos;&lt;/code&gt; 에러가 발생합니다. 패키지를 제거할 때는 &lt;strong&gt;설정 파일, 의존성, import 경로&lt;/strong&gt;를 모두 확인해야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정리: 체크리스트&lt;/h2&gt;
&lt;p&gt;Gatsby 블로그에서 GitHub Actions 빌드가 실패했다면 아래 순서로 확인하세요.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;워크플로우 파일의 Actions 버전&lt;/strong&gt;이 deprecated되지 않았는지 확인&lt;/li&gt;
&lt;li&gt;버전 업데이트 후에도 빌드가 실패하면, &lt;strong&gt;플러그인 호환성 문제&lt;/strong&gt;를 의심&lt;/li&gt;
&lt;li&gt;사용하지 않는 플러그인은 &lt;strong&gt;설정 + 패키지 + import&lt;/strong&gt; 3곳 모두 정리&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby clean&lt;/code&gt;으로 캐시를 지우고 다시 빌드&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;GitHub Actions v3는 언제부터 deprecated인가요?&lt;/h3&gt;
&lt;p&gt;2024년 4월에 deprecation이 공지되었고, 이후 단계적으로 강제 실패 처리되었습니다. 자세한 내용은 &lt;a href=&quot;https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/&quot;&gt;GitHub 공식 변경 로그&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;사용 중인 플러그인이 호환되지 않으면 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;해당 플러그인이 필수라면 호환되는 버전 조합을 찾아야 합니다. 선택적이라면 제거하는 것이 가장 깔끔합니다. &lt;code class=&quot;language-text&quot;&gt;gatsby-transformer-remark&lt;/code&gt;의 메이저 버전을 내리는 것은 다른 플러그인에 영향을 줄 수 있어 추천하지 않습니다.&lt;/p&gt;
&lt;h3&gt;gatsby clean은 언제 실행해야 하나요?&lt;/h3&gt;
&lt;p&gt;플러그인을 추가/제거했거나, 빌드 에러가 이해되지 않을 때 &lt;code class=&quot;language-text&quot;&gt;npx gatsby clean&lt;/code&gt;을 실행하면 캐시 관련 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 Gatsby 블로그의 &lt;strong&gt;검색 기능을 대폭 개선한 경험&lt;/strong&gt;을 공유합니다. 태그 검색, AND/OR 검색, 관련도 정렬까지 외부 서비스 없이 구현하는 방법을 다룹니다.&lt;/p&gt;
&lt;p&gt;여러분도 Actions 빌드 에러를 겪으셨나요? 어떻게 해결하셨는지 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[윈도우 단축키 완전 정복 — 매일 쓰는 핵심 단축키만 모았습니다]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 마우스 없이도 컴퓨터를 자유자재로 다룰 수 있다면 어떨까요? 솔직히 말하면, 윈도우 단축키는 수백 개가 넘습니다. 하지만 실제로 매일 쓰는 건 20~3…]]></description><link>https://blog.javapark.kr/windows-keyboard-shortcuts/</link><guid isPermaLink="false">https://blog.javapark.kr/windows-keyboard-shortcuts/</guid><pubDate>Sat, 04 Apr 2026 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;마우스 없이도 컴퓨터를 자유자재로 다룰 수 있다면 어떨까요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 말하면, 윈도우 단축키는 수백 개가 넘습니다. 하지만 &lt;strong&gt;실제로 매일 쓰는 건 20~30개 정도&lt;/strong&gt;면 충분합니다. 오늘은 &quot;진짜 쓰는 것만&quot; 카테고리별로 정리해봤습니다. 결론부터 말씀드리면 — 이 글에 나온 단축키만 1주일 연습하면, 작업 속도가 눈에 띄게 빨라집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;윈도우 기본 단축키 — 이것만은 반드시&lt;/h2&gt;
&lt;p&gt;가장 기본이면서도 가장 자주 쓰는 단축키입니다. 이미 아시는 것도 있겠지만, 한 번 더 정리해두면 좋습니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Ctrl + C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;복사&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;잘라내기&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;붙여넣기&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행 취소&lt;/td&gt;
&lt;td&gt;실수했을 때 생명줄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;다시 실행&lt;/td&gt;
&lt;td&gt;취소한 걸 되돌릴 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 선택&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;저장&lt;/td&gt;
&lt;td&gt;수시로 누르는 습관 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + F&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;찾기&lt;/td&gt;
&lt;td&gt;문서, 브라우저, 탐색기 모두 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인쇄&lt;/td&gt;
&lt;td&gt;PDF 저장에도 활용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;F2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이름 바꾸기&lt;/td&gt;
&lt;td&gt;파일/폴더 선택 후 바로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;휴지통으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Shift + Delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;영구 삭제&lt;/td&gt;
&lt;td&gt;휴지통 거치지 않음 (주의!)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;윈도우 키 조합 단축키 — 생산성의 핵심&lt;/h2&gt;
&lt;p&gt;윈도우 키(⊞)를 활용한 단축키는 시스템 수준의 기능을 빠르게 실행할 수 있어서, 알아두면 가장 체감이 큽니다.&lt;/p&gt;
&lt;h3&gt;시스템 &amp;#x26; 앱 실행&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;Win + E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파일 탐색기 열기&lt;/td&gt;
&lt;td&gt;가장 자주 쓰는 단축키 중 하나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + I&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;설정 열기&lt;/td&gt;
&lt;td&gt;제어판 찾을 필요 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;화면 잠금&lt;/td&gt;
&lt;td&gt;자리 비울 때 보안 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행 창 열기&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;cmd&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;calc&lt;/code&gt; 등 빠른 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + S&lt;/code&gt; (또는 &lt;code class=&quot;language-text&quot;&gt;Win + Q&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;앱, 파일, 설정 통합 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빠른 링크 메뉴&lt;/td&gt;
&lt;td&gt;장치 관리자, 터미널 등 고급 메뉴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + .&lt;/code&gt; (마침표)&lt;/td&gt;
&lt;td&gt;이모지 패널&lt;/td&gt;
&lt;td&gt;이모지, 특수문자, 가오모지 입력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + 숫자(1~9)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업표시줄 앱 실행&lt;/td&gt;
&lt;td&gt;1번째~9번째 고정 앱 바로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;창(Window) 관리 단축키&lt;/h3&gt;
&lt;p&gt;멀티태스킹의 핵심입니다. 이 단축키들을 익히면 마우스로 창을 드래그할 일이 거의 없어집니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Win + ←&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;Win + →&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;창을 화면 왼쪽/오른쪽 반으로 스냅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + ↑&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;창 최대화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + ↓&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;창 최소화 (최대화 상태면 원래 크기로)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스냅 레이아웃 선택 (Windows 11)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;바탕화면 보기/복귀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 창 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Home&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 창 제외 모두 최소화 (설정에서 제목 표시줄 흔들기 활성화 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;열린 창 간 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + F4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 창/프로그램 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 보기 (가상 데스크톱 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;가상 데스크톱 단축키 — 모니터 하나로도 충분합니다&lt;/h2&gt;
&lt;p&gt;윈도우 11의 가상 데스크톱 기능은 모니터가 하나뿐인 환경에서 특히 유용합니다. 업무용 데스크톱, 개인용 데스크톱을 분리해서 사용해보세요.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 가상 데스크톱 만들기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + ←&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;→&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데스크톱 간 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + F4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 가상 데스크톱 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 데스크톱 &amp;#x26; 창 보기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;저는 데스크톱 1번에 업무(슬랙, 이메일), 2번에 개발(VS Code, 터미널), 3번에 참고자료(브라우저)를 배치합니다. &lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + ←/→&lt;/code&gt;로 순식간에 전환하니까, 듀얼 모니터 부럽지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;스크린샷 &amp;#x26; 클립보드 단축키 — 자료 정리의 필수 기능&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;Win + Shift + S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;화면 캡처 (영역 선택)&lt;/td&gt;
&lt;td&gt;가장 많이 쓰는 스크린샷 방법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;PrtScn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 화면 캡처&lt;/td&gt;
&lt;td&gt;클립보드에 복사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + PrtScn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 화면 캡처 후 자동 저장&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;사진/스크린샷&lt;/code&gt; 폴더에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클립보드 히스토리&lt;/td&gt;
&lt;td&gt;이전에 복사한 내용 목록에서 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Win + V&lt;/code&gt;는 처음 사용 시 활성화가 필요합니다. 한 번 켜두면 복사 이력이 쌓이기 때문에, &quot;아까 복사한 거 어디 갔지?&quot; 하는 상황이 사라집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;브라우저 단축키 — 크롬, 엣지 공통&lt;/h2&gt;
&lt;p&gt;웹 브라우저에서의 단축키도 업무 속도에 큰 영향을 줍니다. 크롬과 엣지 모두 동일하게 작동합니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Ctrl + T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 탭 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 탭 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;닫은 탭 다시 열기 (최대 10개)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;다음 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이전 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;주소창 포커스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + 숫자(1~8)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;해당 번째 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + 9&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;마지막 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;F5&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;Ctrl + R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새로고침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + Delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인터넷 사용 기록 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;특히 &lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + T&lt;/code&gt;는 실수로 탭을 닫았을 때 정말 유용합니다. 직접 써보면 &quot;이걸 왜 이제 알았지?&quot; 하게 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;파일 탐색기 단축키 — 파일 관리가 빨라집니다&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;Ctrl + N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 탐색기 창 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 폴더 만들기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + ←&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;→&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;뒤로/앞으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + ↑&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;상위 폴더로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + D&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;Ctrl + L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;경로 표시줄 포커스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;선택한 항목의 속성 보기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;F2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이름 바꾸기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;F5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새로고침&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;팁:&lt;/strong&gt; 파일 탐색기 상단의 커맨드 바는 선택한 파일 유형에 따라 관련 작업(복사, 이동, 압축 해제 등) 아이콘이 자동으로 바뀝니다. &lt;code class=&quot;language-text&quot;&gt;Alt&lt;/code&gt; 키를 누르면 키보드로 각 메뉴에 접근할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;개발자를 위한 윈도우 단축키 추가 팁&lt;/h2&gt;
&lt;p&gt;개발자라면 아래 단축키도 함께 익혀두면 좋습니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;Win + `&lt;/code&gt; (백틱)&lt;/td&gt;
&lt;td&gt;Windows 터미널 Quake 모드 (터미널 앱 설치 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 관리자 바로 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Pause&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;시스템 정보 (설정 &gt; 정보 페이지로 이동)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Xbox Game Bar (화면 녹화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서식 없이 붙여넣기 (브라우저, VS Code 등 지원 앱에서만 동작)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Win + `&lt;/code&gt;은 Windows 터미널의 &lt;strong&gt;Quake 모드&lt;/strong&gt;로, 화면 상단에 터미널이 슬라이드로 나타납니다. Windows 터미널이 설치되어 있어야 동작합니다. &lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + V&lt;/code&gt;는 OS 기본 단축키가 아니라 앱별 지원 기능이지만, 크롬·엣지·VS Code 등 개발자가 주로 쓰는 앱에서는 대부분 동작합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;윈도우 단축키 빠르게 익히는 3단계 방법&lt;/h2&gt;
&lt;p&gt;단축키를 한꺼번에 외우려고 하면 오히려 역효과입니다. 아래 방법을 추천합니다.&lt;/p&gt;
&lt;h3&gt;1단계: 5개만 먼저 (1주차)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Win + E&lt;/code&gt; (탐색기), &lt;code class=&quot;language-text&quot;&gt;Win + L&lt;/code&gt; (잠금), &lt;code class=&quot;language-text&quot;&gt;Win + D&lt;/code&gt; (바탕화면)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Shift + S&lt;/code&gt; (캡처), &lt;code class=&quot;language-text&quot;&gt;Win + V&lt;/code&gt; (클립보드)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2단계: 창 관리 추가 (2주차)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Win + ←/→&lt;/code&gt; (스냅), &lt;code class=&quot;language-text&quot;&gt;Alt + Tab&lt;/code&gt; (창 전환)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Tab&lt;/code&gt; (작업 보기), &lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + D&lt;/code&gt; (가상 데스크톱)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3단계: 브라우저 &amp;#x26; 탐색기 (3주차)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + W/T/Shift+T&lt;/code&gt; (탭 관리)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + N&lt;/code&gt; (새 폴더), &lt;code class=&quot;language-text&quot;&gt;Alt + ←&lt;/code&gt; (뒤로)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;핵심은 &lt;strong&gt;한 번에 많이 외우는 것이 아니라, 매일 쓰는 동작부터 바꾸는 것&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;윈도우 단축키 한 장 요약표&lt;/h2&gt;
&lt;p&gt;가장 활용도 높은 단축키만 한 장으로 정리했습니다. 모니터 옆에 붙여두고 쓰세요.&lt;/p&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;필수&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + C/X/V/Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;복사/잘라내기/붙여넣기/취소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파일 탐색기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + I&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;화면 잠금&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;창 관리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + ←/→&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;화면 분할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;창 관리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Alt + Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;창 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;창 관리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;바탕화면&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;캡처&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Shift + S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;영역 캡처&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클립보드&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클립보드 히스토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상 데스크톱&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 데스크톱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상 데스크톱&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Win + Ctrl + ←/→&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데스크톱 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;브라우저&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + T/W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 탭/탭 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;브라우저&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Ctrl + Shift + T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;닫은 탭 복구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q. 윈도우 단축키가 안 먹힐 때는 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;일부 단축키는 특정 앱이 포커스를 가로채면 동작하지 않을 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;Win + D&lt;/code&gt;로 바탕화면으로 나간 뒤 다시 시도하거나, 게임 모드가 활성화되어 있다면 &lt;code class=&quot;language-text&quot;&gt;Win + G&lt;/code&gt;에서 게임 모드를 꺼보세요. 키보드 드라이버 문제일 수도 있으니, 설정 &gt; 접근성 &gt; 키보드에서 고정 키, 필터 키가 꺼져 있는지 확인하세요.&lt;/p&gt;
&lt;h3&gt;Q. 윈도우 10과 11의 단축키 차이가 있나요?&lt;/h3&gt;
&lt;p&gt;대부분의 단축키는 동일합니다. 윈도우 11에서 새로 추가된 주요 단축키는 &lt;code class=&quot;language-text&quot;&gt;Win + Z&lt;/code&gt;(스냅 레이아웃), &lt;code class=&quot;language-text&quot;&gt;Win + C&lt;/code&gt;(Copilot 실행), &lt;code class=&quot;language-text&quot;&gt;Win + W&lt;/code&gt;(위젯) 등이 있습니다.&lt;/p&gt;
&lt;h3&gt;Q. 자주 쓰는 프로그램에 나만의 단축키를 지정할 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, 가능합니다. 프로그램의 바로가기 아이콘을 우클릭 → 속성 → &quot;바로 가기 키&quot; 항목에 원하는 키 조합을 입력하면 됩니다. &lt;code class=&quot;language-text&quot;&gt;Ctrl + Alt + 원하는 키&lt;/code&gt; 형태로 지정할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;오늘 소개한 단축키 중 &lt;strong&gt;하나만이라도 오늘부터 써보세요.&lt;/strong&gt; 1주일이면 손에 붙고, 한 달이면 마우스 없이도 대부분의 작업이 가능해집니다.&lt;/p&gt;
&lt;p&gt;더 자세한 전체 단축키 목록은 &lt;a href=&quot;https://support.microsoft.com/ko-kr/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec&quot;&gt;Microsoft 공식 키보드 단축키 문서&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;여러분이 가장 자주 쓰는 윈도우 단축키는 뭔가요? 댓글로 알려주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[블로그 댓글을 Utterances에서 Giscus로 전환한 이유와 방법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "블로그 댓글 시스템, Utterances 쓰고 있는데 굳이 바꿔야 하나요?" 저도 그렇게 생각했습니다. Utterances는 가볍고 깔끔하니까요. 하지만 직접 Giscus…]]></description><link>https://blog.javapark.kr/utterances-to-giscus/</link><guid isPermaLink="false">https://blog.javapark.kr/utterances-to-giscus/</guid><pubDate>Sat, 04 Apr 2026 20:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;블로그 댓글 시스템, Utterances 쓰고 있는데 굳이 바꿔야 하나요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저도 그렇게 생각했습니다. Utterances는 가볍고 깔끔하니까요. 하지만 직접 Giscus로 전환해보니 — &lt;strong&gt;대댓글, 포스트 리액션, 실시간 다크모드 전환&lt;/strong&gt;까지, 체감 차이가 확실했습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 왜 전환했는지, 어떻게 전환했는지, 실제 코드까지 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Utterances vs Giscus — 뭐가 다른가&lt;/h2&gt;
&lt;p&gt;둘 다 GitHub 기반 무료 댓글 시스템이지만, 저장 방식이 다릅니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Utterances&lt;/th&gt;
&lt;th&gt;Giscus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;저장소&lt;/td&gt;
&lt;td&gt;GitHub &lt;strong&gt;Issues&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub &lt;strong&gt;Discussions&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대댓글&lt;/td&gt;
&lt;td&gt;불가 (일렬 나열)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;스레드 지원&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스트 리액션&lt;/td&gt;
&lt;td&gt;댓글에만 리액션&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;포스트 자체에 리액션&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다크모드&lt;/td&gt;
&lt;td&gt;테마별 컴포넌트 2개 필요&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;실시간 테마 전환&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;언어&lt;/td&gt;
&lt;td&gt;영어&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;한국어 포함 다국어&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개발 상태&lt;/td&gt;
&lt;td&gt;유지보수 모드&lt;/td&gt;
&lt;td&gt;활발한 개발 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비용&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;왜 전환했나&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;대댓글이 안 됨&lt;/strong&gt; — Utterances는 모든 댓글이 일렬로 나열됩니다. 대화가 길어지면 누가 누구에게 답한 건지 알기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다크모드 코드가 지저분함&lt;/strong&gt; — Utterances는 테마 변경 시 컴포넌트를 다시 렌더링해야 해서, light/dark 두 개의 Utterances를 숨기고 보여주는 트릭이 필요했습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리액션 기능&lt;/strong&gt; — Giscus는 포스트 자체에 리액션을 달 수 있어서, 댓글을 쓰지 않아도 독자의 반응을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;전환 과정 — 5단계로 끝&lt;/h2&gt;
&lt;h3&gt;1단계: GitHub Discussions 활성화&lt;/h3&gt;
&lt;p&gt;GitHub 레포 → Settings → Features → &lt;strong&gt;Discussions 체크&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# GitHub CLI로도 가능합니다&lt;/span&gt;
gh api repos/username/repo &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; PATCH &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;has_discussions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: Giscus 앱 설치&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/apps/giscus&quot;&gt;github.com/apps/giscus&lt;/a&gt;에서 Install → 레포 선택&lt;/p&gt;
&lt;h3&gt;3단계: Giscus 설정값 확인&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://giscus.app/ko&quot;&gt;giscus.app&lt;/a&gt;에서 레포를 입력하면 설정값이 자동 생성됩니다. 필요한 값:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;repo&lt;/code&gt; — 레포 경로 (예: &lt;code class=&quot;language-text&quot;&gt;javapark/javapark.github.io&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;repoId&lt;/code&gt; — 레포 고유 ID&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;category&lt;/code&gt; — Discussion 카테고리 (보통 &lt;code class=&quot;language-text&quot;&gt;General&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;categoryId&lt;/code&gt; — 카테고리 고유 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GraphQL로 직접 확인할 수도 있습니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;gh api graphql &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;
{
  repository(owner: &quot;username&quot;, name: &quot;repo&quot;) {
    id
    discussionCategories(first: 10) {
      nodes { id name }
    }
  }
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4단계: 패키지 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @giscus/react&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;5단계: 코드 교체&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before (Utterances):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Utterances &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utterances-react-component&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; theme &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;spinner&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setSpinner&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setSpinner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;spinner &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Spinner&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;isHidden&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;spinner&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;isHidden&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;light&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Utterances&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user/repo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-dark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;issueTerm&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;isHidden&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Utterances&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user/repo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-light&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;issueTerm&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HiddenWrapper&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After (Giscus):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Giscus &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@giscus/react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; theme &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Giscus&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user/repo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;repoId&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;R_xxxxx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;General&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;categoryId&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DIC_xxxxx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;reactionsEnabled&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;emitMetadata&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;inputPosition&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;top&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dark&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dark_dimmed&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;light&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ko&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&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;ul&gt;
&lt;li&gt;Utterances: 스피너 + HiddenWrapper 2개로 light/dark 전환 → &lt;strong&gt;30줄&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Giscus: &lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt; prop만 바꾸면 실시간 전환 → &lt;strong&gt;15줄&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드가 절반으로 줄었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Giscus 주요 설정 옵션&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;mapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;포스트 URL 경로로 Discussion 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;reactionsEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;1&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;포스트 리액션 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;inputPosition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;top&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;댓글 입력창을 상단에 배치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;light&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;dark_dimmed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테마 (다크모드 연동)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;lang&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ko&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;한국어 UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;사용 가능한 테마&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;light&lt;/code&gt; — 밝은 테마&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;dark&lt;/code&gt; — 어두운 테마&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;dark_dimmed&lt;/code&gt; — 약간 밝은 다크 테마 (추천)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;transparent_dark&lt;/code&gt; — 투명 다크&lt;/li&gt;
&lt;li&gt;커스텀 CSS URL도 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;전환 후 달라진 점&lt;/h2&gt;
&lt;h3&gt;대댓글 지원&lt;/h3&gt;
&lt;p&gt;댓글에 답글을 달 수 있어서 대화가 자연스럽게 이어집니다. Utterances에서는 &quot;@사용자&quot; 멘션으로 대신해야 했던 것이 스레드로 깔끔하게 정리됩니다.&lt;/p&gt;
&lt;h3&gt;포스트 리액션&lt;/h3&gt;
&lt;p&gt;댓글을 쓰지 않아도 포스트에 리액션을 남길 수 있습니다. &quot;이 글이 도움이 됐다&quot;는 피드백을 부담 없이 받을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;코드 간소화&lt;/h3&gt;
&lt;p&gt;다크모드 전환을 위한 HiddenWrapper 트릭, 스피너 로딩 등 불필요한 코드를 모두 제거했습니다. Giscus는 &lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt; prop만 변경하면 자동으로 테마가 전환됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기존 Utterances 댓글은 어떻게 되나요?&lt;/h2&gt;
&lt;p&gt;Utterances로 작성된 기존 댓글은 GitHub Issues에 그대로 남아 있습니다. Giscus는 GitHub Discussions에 새로운 스레드를 만들기 때문에, 기존 댓글과 새 댓글은 별도로 관리됩니다.&lt;/p&gt;
&lt;p&gt;기존 댓글을 Discussions로 이전하려면 GitHub API를 사용해 Issues의 댓글을 Discussions로 복사하는 스크립트를 작성해야 합니다. 다만, 댓글이 많지 않다면 새로 시작하는 것을 추천합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. Giscus도 GitHub 계정이 필요한가요?&lt;/h3&gt;
&lt;p&gt;네. Utterances와 마찬가지로 댓글을 작성하려면 GitHub 계정으로 로그인해야 합니다. 개발 블로그라면 대부분의 독자가 GitHub 계정을 가지고 있으므로 큰 문제가 되지 않습니다.&lt;/p&gt;
&lt;h3&gt;Q2. Giscus는 무료인가요?&lt;/h3&gt;
&lt;p&gt;네. 오픈소스이며 완전 무료입니다. 광고도 없고, 트래킹도 없습니다. 모든 데이터는 여러분의 GitHub 레포에 저장됩니다.&lt;/p&gt;
&lt;h3&gt;Q3. Next.js에서도 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;네. &lt;code class=&quot;language-text&quot;&gt;@giscus/react&lt;/code&gt; 패키지는 React 기반이라 Next.js, Gatsby, CRA 등 모든 React 프로젝트에서 사용할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Utterances에서 Giscus로의 전환은 &lt;strong&gt;5분이면 끝나는 작업&lt;/strong&gt;입니다. 대댓글, 리액션, 실시간 테마 전환 — 이 세 가지만으로도 전환할 가치가 충분합니다.&lt;/p&gt;
&lt;p&gt;여러분의 블로그에도 Giscus를 적용해보세요. 아래 Giscus 댓글로 첫 댓글을 남겨주시면 더 좋겠습니다!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고 자료:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://giscus.app/ko&quot;&gt;Giscus 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/giscus/giscus&quot;&gt;Giscus GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@giscus/react&quot;&gt;@giscus/react NPM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[블로그에 광고와 댓글 시스템 달기 — 수익화와 소통]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "블로그에 글만 올리면 끝인가요? 수익도 낼 수 있고, 독자와 소통도 할 수 있다면 좋겠는데..." GitHub Pages…]]></description><link>https://blog.javapark.kr/06-ads-and-comments/</link><guid isPermaLink="false">https://blog.javapark.kr/06-ads-and-comments/</guid><pubDate>Sat, 04 Apr 2026 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;블로그에 글만 올리면 끝인가요? 수익도 낼 수 있고, 독자와 소통도 할 수 있다면 좋겠는데...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub Pages 블로그도 광고를 달아 수익을 만들 수 있고, 댓글 시스템을 붙여 독자와 소통할 수 있습니다. 결론부터 말씀드리면 — 정적 사이트라서 불가능할 것 같지만, 방법은 충분히 있습니다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 Google AdSense와 쿠팡 파트너스로 수익화하는 방법, 그리고 Utterances로 GitHub 기반 댓글 시스템을 구축하는 방법을 다룹니다. &quot;GitHub 블로그 A to Z&quot; 시리즈의 마지막 편입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;블로그 수익화 옵션 비교&lt;/h2&gt;
&lt;p&gt;GitHub Pages 블로그에 적용할 수 있는 수익화 방법은 크게 세 가지입니다.&lt;/p&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;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;Google AdSense&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;승인 심사 있음&lt;/td&gt;
&lt;td&gt;CPC (클릭당 과금)&lt;/td&gt;
&lt;td&gt;가장 보편적, 심사 통과가 관건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;쿠팡 파트너스&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;td&gt;가입 즉시 가능&lt;/td&gt;
&lt;td&gt;CPS (구매당 수수료)&lt;/td&gt;
&lt;td&gt;한국 사용자 대상으로 효과적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제휴 마케팅&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;업체별 다름&lt;/td&gt;
&lt;td&gt;건당 수수료&lt;/td&gt;
&lt;td&gt;특정 상품/서비스 추천&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;초보 블로거라면 &lt;strong&gt;쿠팡 파트너스를 먼저 시작&lt;/strong&gt;하고, 블로그가 어느 정도 성장한 뒤 &lt;strong&gt;AdSense 승인을 신청&lt;/strong&gt;하는 것을 추천합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Google AdSense 신청 및 설정&lt;/h2&gt;
&lt;h3&gt;AdSense 승인 조건&lt;/h3&gt;
&lt;p&gt;구글이 공식적으로 밝히지는 않지만, 경험적으로 알려진 조건들이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;양질의 포스팅이 15개 이상&lt;/li&gt;
&lt;li&gt;각 포스팅이 1,000자 이상의 충실한 내용&lt;/li&gt;
&lt;li&gt;개인정보처리방침(Privacy Policy) 페이지 존재&lt;/li&gt;
&lt;li&gt;커스텀 도메인 사용 (github.io 도메인으로는 승인이 어려울 수 있음)&lt;/li&gt;
&lt;li&gt;저작권 문제가 없는 콘텐츠&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AdSense 신청 절차&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.google.com/adsense&quot;&gt;Google AdSense&lt;/a&gt;에 접속하여 가입합니다&lt;/li&gt;
&lt;li&gt;블로그 URL(&lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr&lt;/code&gt;)을 등록합니다&lt;/li&gt;
&lt;li&gt;AdSense에서 제공하는 인증 코드를 블로그의 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;head&gt;&lt;/code&gt;에 추가합니다&lt;/li&gt;
&lt;li&gt;심사 결과를 기다립니다 (보통 1~2주)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Gatsby에 AdSense 코드 삽입하기&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby-ssr.js&lt;/code&gt; 파일을 사용해 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;head&gt;&lt;/code&gt;에 스크립트를 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-ssr.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onRenderBody&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; setHeadComponents &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setHeadComponents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script
      key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;adsense&quot;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt;
      src&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX&quot;&lt;/span&gt;
      crossOrigin&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ca-pub-XXXXXXXXXXXXXXXX&lt;/code&gt; 부분은 AdSense에서 제공하는 본인의 퍼블리셔 ID로 교체합니다.&lt;/p&gt;
&lt;h3&gt;포스팅 내에 광고 배치하기&lt;/h3&gt;
&lt;p&gt;승인이 완료되면, 광고 컴포넌트를 만들어 원하는 위치에 삽입할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/AdSense.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AdSense &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; slot&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; format &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; responsive &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adsbygoogle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adsbygoogle &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;AdSense error:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;textAlign&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20px 0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ins&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;adsbygoogle&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;data-ad-client&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ca-pub-XXXXXXXXXXXXXXXX&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;data-ad-slot&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;slot&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;data-ad-format&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;format&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;data-full-width-responsive&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;responsive&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; AdSense&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;포스팅 템플릿에서 사용할 때는 다음과 같이 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 포스팅 본문 아래에 삽입&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AdSense&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;slot&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1234567890&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;쿠팡 파트너스 가입 및 광고 삽입&lt;/h2&gt;
&lt;p&gt;쿠팡 파트너스는 가입 후 바로 사용할 수 있어서 블로그 초기부터 적용하기 좋습니다. 방문자가 링크를 통해 24시간 내에 구매하면 수수료(보통 3~5%)를 받을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;가입 절차&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://partners.coupang.com/&quot;&gt;쿠팡 파트너스&lt;/a&gt;에 접속하여 가입합니다&lt;/li&gt;
&lt;li&gt;미디어(블로그) 정보를 등록합니다&lt;/li&gt;
&lt;li&gt;승인 후 광고 소재를 생성할 수 있습니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;다이나믹 배너 삽입 (iframe 방식)&lt;/h3&gt;
&lt;p&gt;쿠팡 파트너스에서 제공하는 다이나믹 배너를 Gatsby 컴포넌트로 만들어봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/CoupangAd.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; CoupangAd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;100%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20px 0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;textAlign&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;iframe&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;frameBorder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;scrolling&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;referrerPolicy&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;unsafe-url&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;쿠팡 파트너스&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;maxWidth&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;100%&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;fontSize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;11px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#888&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4px&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를
        제공받습니다.
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; CoupangAd&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;쿠팡 파트너스 사이트에서 생성한 다이나믹 배너의 &lt;code class=&quot;language-text&quot;&gt;src&lt;/code&gt; 값을 복사해서 사용합니다.&lt;/p&gt;
&lt;h3&gt;텍스트 링크 방식&lt;/h3&gt;
&lt;p&gt;배너 대신 본문에 자연스럽게 상품 링크를 넣는 방법도 있습니다. 리뷰 글이나 추천 글에 적합합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;개발에 사용 중인 키보드가 궁금하시다면, &lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;제가 사용하는 키보드&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;쿠팡_파트너스_링크&lt;/span&gt;)&lt;/span&gt;를 참고해보세요.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;광고 위치 전략&lt;/h3&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;포스팅 상단 (제목 아래)&lt;/td&gt;
&lt;td&gt;노출률 높음&lt;/td&gt;
&lt;td&gt;사용자 이탈 위험&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스팅 중간&lt;/td&gt;
&lt;td&gt;자연스러운 노출&lt;/td&gt;
&lt;td&gt;내용 흐름을 깨지 않도록 배치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스팅 하단 (본문 끝)&lt;/td&gt;
&lt;td&gt;글을 다 읽은 독자에게 노출&lt;/td&gt;
&lt;td&gt;가장 무난한 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사이드바&lt;/td&gt;
&lt;td&gt;지속적 노출&lt;/td&gt;
&lt;td&gt;모바일에서는 안 보일 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;저는 포스팅 하단에 하나, 긴 글의 경우 중간에 하나 정도 배치하는 것을 추천합니다. 솔직히 광고를 너무 많이 넣으면 독자가 떠납니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;댓글 시스템 비교&lt;/h2&gt;
&lt;p&gt;GitHub Pages는 서버가 없는 정적 사이트이기 때문에, 자체적으로 댓글 기능을 구현할 수 없습니다. 외부 서비스를 활용해야 합니다.&lt;/p&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;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;&lt;strong&gt;Disqus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;자체 플랫폼&lt;/td&gt;
&lt;td&gt;소셜 로그인&lt;/td&gt;
&lt;td&gt;무료 (광고 포함)&lt;/td&gt;
&lt;td&gt;설정 간편&lt;/td&gt;
&lt;td&gt;광고 삽입, 무거움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Utterances&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub Issues&lt;/td&gt;
&lt;td&gt;GitHub 로그인&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;가볍고 깔끔&lt;/td&gt;
&lt;td&gt;GitHub 계정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Giscus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub Discussions&lt;/td&gt;
&lt;td&gt;GitHub 로그인&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;스레드 지원, 리액션&lt;/td&gt;
&lt;td&gt;GitHub 계정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;개발 블로그라면 &lt;strong&gt;Utterances&lt;/strong&gt; 또는 &lt;strong&gt;Giscus&lt;/strong&gt;를 추천합니다. 독자 대부분이 개발자이고 GitHub 계정을 가지고 있을 테니까요. 이번 글에서는 설정이 간단한 Utterances를 기준으로 설명하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Utterances 설정하기&lt;/h2&gt;
&lt;p&gt;Utterances는 GitHub Issues를 댓글 저장소로 활용하는 오픈소스 위젯입니다. 누군가 댓글을 달면, 해당 포스팅에 대응하는 GitHub Issue가 자동으로 생성됩니다.&lt;/p&gt;
&lt;h3&gt;1단계: utterances 앱 설치&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apps/utterances&quot;&gt;utterances 앱 페이지&lt;/a&gt;에 접속합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install&lt;/strong&gt; 버튼을 클릭합니다&lt;/li&gt;
&lt;li&gt;블로그 레포지토리(예: &lt;code class=&quot;language-text&quot;&gt;javapark/javapark.github.io&lt;/code&gt;)를 선택합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install&lt;/strong&gt; 을 확인합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2단계: Utterances 컴포넌트 만들기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/Utterances.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useRef &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Utterances &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; repo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github-light&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; containerRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;script&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://utteranc.es/client.js&quot;&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;async &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;repo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; repo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;issue-term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pathname&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;theme&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;crossorigin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 기존 스크립트가 있으면 제거&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; existingScript &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;script&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingScript&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      existingScript&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;repo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerRef&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Utterances&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3단계: 포스팅 템플릿에 삽입&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/templates/blog-post.js (또는 해당 템플릿 파일)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Utterances &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../components/Utterances&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 본문 아래에 추가&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Utterances&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;javapark/javapark.github.io&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-light&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Utterances 설정 옵션&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;repo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;사용자명/레포명&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;댓글이 저장될 레포지토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;issue-term&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issue 매핑 기준 (URL 경로 기준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;issue-term&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issue 매핑 기준 (포스팅 제목 기준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;github-light&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;라이트 테마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;github-dark&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;다크 테마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;preferred-color-scheme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OS 설정에 따라 자동 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;issue-term&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;을 추천합니다. URL이 바뀌지 않는 한 댓글이 유지되기 때문입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;다크모드 대응 댓글 시스템&lt;/h2&gt;
&lt;p&gt;gatsby-starter-hoodie 테마는 다크모드를 지원합니다. 블로그가 다크모드로 전환되면 댓글 위젯도 함께 변해야 자연스럽습니다.&lt;/p&gt;
&lt;h3&gt;테마 상태에 따라 Utterances 테마 변경&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/Utterances.js (다크모드 대응 버전)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useRef&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useContext &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ThemeContext &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../contexts/ThemeContext&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Utterances&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; repo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; containerRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; isDarkMode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ThemeContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isDarkMode &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github-dark&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github-light&quot;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 기존 iframe 제거&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; existingIframe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.utterances&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingIframe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      existingIframe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;script&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://utteranc.es/client.js&quot;&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;async &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;repo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; repo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;issue-term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pathname&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;theme&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;crossorigin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;repo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerRef&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Utterances&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;메시지 방식으로 테마 변경 (iframe 재생성 없이)&lt;/h3&gt;
&lt;p&gt;위 방법은 테마가 변경될 때마다 iframe을 다시 만들기 때문에 깜빡임이 발생합니다. &lt;code class=&quot;language-text&quot;&gt;postMessage&lt;/code&gt;를 활용하면 더 부드럽게 전환할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; iframe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.utterances-frame&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iframe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    iframe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentWindow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;set-theme&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;https://utteranc.es&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 코드를 기존 컴포넌트의 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;와 별도로 추가하면, 다크모드 전환 시 댓글 위젯이 부드럽게 테마를 변경합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;광고 배치 팁 — 사용자 경험과 수익의 균형&lt;/h2&gt;
&lt;p&gt;솔직히 말하면, 광고는 많이 넣을수록 수익이 올라갈 것 같지만 현실은 반대인 경우가 많습니다.&lt;/p&gt;
&lt;h3&gt;광고 배치 원칙&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;본문 내용을 방해하지 않기&lt;/strong&gt; — 독자가 글을 읽다가 광고에 가로막히면 이탈합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한 페이지에 3개 이하&lt;/strong&gt; — AdSense 정책상 제한은 없지만, 3개를 넘기면 사용자 경험이 급격히 나빠집니다&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;/ol&gt;
&lt;h3&gt;추천 배치 패턴&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[포스팅 제목]
[본문 시작]
...
(본문의 40~50% 지점)
--- 쿠팡 파트너스 배너 ---
...
[본문 끝]
--- AdSense 광고 ---
--- 댓글 시스템 ---&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 패턴이면 독자가 글을 읽는 흐름을 크게 방해하지 않으면서도 광고 노출을 확보할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;시리즈 마무리 — GitHub 블로그 A to Z 정리&lt;/h2&gt;
&lt;p&gt;&quot;GitHub 블로그 A to Z&quot; 시리즈 6편을 통해 블로그의 생성부터 수익화까지 전 과정을 다뤘습니다. 지금까지의 여정을 정리해보겠습니다.&lt;/p&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;1편&lt;/td&gt;
&lt;td&gt;시작하기&lt;/td&gt;
&lt;td&gt;Gatsby + gatsby-starter-hoodie로 블로그 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2편&lt;/td&gt;
&lt;td&gt;스킨 커스터마이징&lt;/td&gt;
&lt;td&gt;테마 색상, 폰트, 레이아웃 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3편&lt;/td&gt;
&lt;td&gt;마크다운과 카테고리&lt;/td&gt;
&lt;td&gt;포스팅 작성법, 시리즈/태그 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4편&lt;/td&gt;
&lt;td&gt;SEO 최적화&lt;/td&gt;
&lt;td&gt;메타태그, sitemap, 구조화 데이터, Search Console&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5편&lt;/td&gt;
&lt;td&gt;자동 배포&lt;/td&gt;
&lt;td&gt;GitHub Actions CI/CD 파이프라인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6편&lt;/td&gt;
&lt;td&gt;광고와 댓글&lt;/td&gt;
&lt;td&gt;AdSense, 쿠팡 파트너스, Utterances&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;블로그 운영을 시작하시는 분들께&lt;/h3&gt;
&lt;p&gt;블로그는 기술적 완성도보다 &lt;strong&gt;꾸준한 글쓰기&lt;/strong&gt;가 훨씬 중요합니다. 완벽한 디자인과 모든 기능이 갖춰져야 시작할 수 있는 게 아닙니다. 글 하나라도 먼저 쓰고, 나머지는 천천히 개선해 나가시면 됩니다.&lt;/p&gt;
&lt;p&gt;직접 조사하고 경험한 내용을 바탕으로 이 시리즈를 작성했는데, 여러분의 블로그 시작에 조금이라도 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q. AdSense 승인이 거절되면 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;거절 사유를 메일로 받게 됩니다. 가장 흔한 거절 사유는 &quot;콘텐츠 불충분&quot;입니다. 양질의 포스팅을 5~10개 더 작성한 뒤 재신청하면 됩니다. 재신청 횟수에는 제한이 없으니, 콘텐츠를 보강하면서 꾸준히 신청해보세요. 커스텀 도메인을 사용하는 것도 승인률을 높이는 데 도움이 됩니다.&lt;/p&gt;
&lt;h3&gt;Q. Utterances 대신 Giscus를 사용하는 것이 더 좋나요?&lt;/h3&gt;
&lt;p&gt;둘 다 좋은 선택입니다. Giscus는 GitHub Discussions 기반이라 댓글에 대한 답글(스레드)과 리액션을 지원하는 장점이 있습니다. 설정 방법도 Utterances와 거의 동일합니다. 만약 댓글 간 대화가 활발할 것 같다면 Giscus를, 단순 댓글이면 Utterances를 추천합니다.&lt;/p&gt;
&lt;h3&gt;Q. 쿠팡 파트너스 수익은 어느 정도 나오나요?&lt;/h3&gt;
&lt;p&gt;솔직히 일일 방문자가 100명 이하일 때는 월 수익이 커피 한 잔 값도 안 됩니다. 블로그 트래픽이 핵심이기 때문에, SEO 최적화(4편 참고)와 꾸준한 포스팅으로 방문자를 먼저 늘리는 데 집중하시는 게 좋습니다. 트래픽이 올라오면 자연스럽게 수익도 따라옵니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 블로그 수익화를 어떻게 하고 계신가요? 또는 어떤 댓글 시스템을 사용하고 계신가요? 댓글로 알려주세요.&lt;/p&gt;
&lt;p&gt;&quot;GitHub 블로그 A to Z&quot; 시리즈를 끝까지 읽어주셔서 감사합니다. 앞으로도 블로그 운영에 도움이 되는 글을 꾸준히 올리겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub Actions로 블로그 자동 배포하기 — push만 하면 끝]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "글 수정할 때마다 npm run build 하고 gh-pages 브랜치에 push…]]></description><link>https://blog.javapark.kr/05-github-actions-deploy/</link><guid isPermaLink="false">https://blog.javapark.kr/05-github-actions-deploy/</guid><pubDate>Sat, 04 Apr 2026 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;글 수정할 때마다 npm run build 하고 gh-pages 브랜치에 push하고... 이걸 매번 해야 하나요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 말하면, 수동 배포를 계속하다 보면 글 쓰는 것보다 배포가 더 귀찮아지는 순간이 옵니다. 결론부터 말씀드리면 — GitHub Actions를 설정하면 &lt;code class=&quot;language-text&quot;&gt;git push&lt;/code&gt; 한 번으로 빌드부터 배포까지 전부 자동화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 Gatsby 블로그를 GitHub Actions로 자동 배포하는 CI/CD 파이프라인을 처음부터 끝까지 구축해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;수동 배포의 문제점&lt;/h2&gt;
&lt;p&gt;수동으로 GitHub Pages에 배포하려면 보통 이런 과정을 거칩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1. 빌드&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build

&lt;span class=&quot;token comment&quot;&gt;# 2. gh-pages 브랜치에 배포&lt;/span&gt;
npx gh-pages &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; public

&lt;span class=&quot;token comment&quot;&gt;# 3. 또는 직접 public 폴더를 gh-pages 브랜치에 push&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;로컬 환경에 따라 빌드 결과가 달라질 수 있습니다&lt;/li&gt;
&lt;li&gt;시간이 오래 걸립니다 (특히 Gatsby 빌드는 느린 편)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자동화의 핵심은 단순합니다. &lt;strong&gt;코드를 push하면 나머지는 서버가 알아서 하도록&lt;/strong&gt; 만드는 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub Actions 기본 개념&lt;/h2&gt;
&lt;p&gt;GitHub Actions는 GitHub에서 제공하는 CI/CD(지속적 통합/지속적 배포) 서비스입니다. 레포지토리에 특정 이벤트(push, PR 등)가 발생하면 정의된 작업을 자동으로 실행합니다.&lt;/p&gt;
&lt;h3&gt;핵심 용어 정리&lt;/h3&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;&lt;strong&gt;Workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;자동화 작업의 전체 흐름&lt;/td&gt;
&lt;td&gt;레시피 전체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;워크플로우를 실행하는 트리거&lt;/td&gt;
&lt;td&gt;&quot;주문이 들어오면&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;워크플로우 안의 작업 단위&lt;/td&gt;
&lt;td&gt;레시피의 각 단계 (재료 준비, 조리, 플레이팅)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Job 안의 개별 명령어&lt;/td&gt;
&lt;td&gt;&quot;양파를 잘게 썬다&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;워크플로우가 실행되는 서버&lt;/td&gt;
&lt;td&gt;주방&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;재사용 가능한 Step 묶음&lt;/td&gt;
&lt;td&gt;밀키트 (미리 만들어진 것)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;워크플로우 파일은 &lt;code class=&quot;language-text&quot;&gt;.github/workflows/&lt;/code&gt; 디렉토리에 YAML 형식으로 작성합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;블로그 배포용 workflow 파일 작성&lt;/h2&gt;
&lt;p&gt;프로젝트 루트에 &lt;code class=&quot;language-text&quot;&gt;.github/workflows/deploy.yml&lt;/code&gt; 파일을 만들겠습니다.&lt;/p&gt;
&lt;h3&gt;전체 workflow 파일&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy Blog to GitHub Pages

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; main &lt;span class=&quot;token comment&quot;&gt;# main 브랜치에 push하면 실행&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build-and-deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# 1. 레포지토리 코드 체크아웃&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout repository
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4

      &lt;span class=&quot;token comment&quot;&gt;# 2. Node.js 설정&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup Node.js
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm&quot;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# 3. 의존성 설치&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install dependencies
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm ci

      &lt;span class=&quot;token comment&quot;&gt;# 4. Gatsby 빌드&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build Gatsby site
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm run build
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;PREFIX_PATHS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# 5. GitHub Pages 배포&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy to GitHub Pages
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peaceiris/actions&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;github_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;publish_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./public
          &lt;span class=&quot;token key atrule&quot;&gt;publish_branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 파일 하나면 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; 브랜치에 push할 때마다 자동으로 빌드 후 &lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; 브랜치에 배포됩니다.&lt;/p&gt;
&lt;h3&gt;파일 생성 방법&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 디렉토리 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; .github/workflows

&lt;span class=&quot;token comment&quot;&gt;# 파일 생성 (위 내용을 붙여넣기)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; .github/workflows/deploy.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;각 Step 상세 설명&lt;/h2&gt;
&lt;h3&gt;Step 1: actions/checkout&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout repository
  &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GitHub Actions Runner(서버)에 레포지토리 코드를 가져옵니다. 모든 워크플로우의 첫 번째 단계입니다.&lt;/p&gt;
&lt;h3&gt;Step 2: actions/setup-node&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup Node.js
  &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
  &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Node.js를 설치합니다. &lt;code class=&quot;language-text&quot;&gt;node-version&lt;/code&gt;은 로컬에서 사용하는 버전과 맞추는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;로컬 Node.js 버전 확인 방법:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# v20.11.0 이런 식으로 출력됩니다&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Step 3: npm ci&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install dependencies
  &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm ci&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;npm install&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;npm ci&lt;/code&gt;를 사용합니다. &lt;code class=&quot;language-text&quot;&gt;npm ci&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;package-lock.json&lt;/code&gt;을 기반으로 정확히 동일한 의존성을 설치하므로, 빌드 환경의 일관성을 보장합니다.&lt;/p&gt;
&lt;h3&gt;Step 4: Gatsby 빌드&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build Gatsby site
  &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm run build
  &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;PREFIX_PATHS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt; 명령어를 실행해 정적 파일을 생성합니다. 결과물은 &lt;code class=&quot;language-text&quot;&gt;public/&lt;/code&gt; 폴더에 저장됩니다.&lt;/p&gt;
&lt;h3&gt;Step 5: peaceiris/actions-gh-pages&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy to GitHub Pages
  &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peaceiris/actions&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4
  &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;github_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;publish_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./public
    &lt;span class=&quot;token key atrule&quot;&gt;publish_branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;public/&lt;/code&gt; 폴더의 내용을 &lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; 브랜치에 자동으로 push합니다. &lt;code class=&quot;language-text&quot;&gt;GITHUB_TOKEN&lt;/code&gt;은 별도 설정 없이 GitHub Actions가 자동으로 제공합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;캐시 설정으로 빌드 속도 향상&lt;/h2&gt;
&lt;p&gt;Gatsby 빌드는 처음에는 시간이 꽤 걸립니다. 캐시를 설정하면 빌드 시간을 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;h3&gt;npm 캐시 (이미 적용됨)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;actions/setup-node&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;cache: &quot;npm&quot;&lt;/code&gt; 옵션으로 npm 패키지 캐시는 이미 적용되어 있습니다.&lt;/p&gt;
&lt;h3&gt;Gatsby 캐시 추가&lt;/h3&gt;
&lt;p&gt;Gatsby는 &lt;code class=&quot;language-text&quot;&gt;.cache&lt;/code&gt; 폴더에 빌드 캐시를 저장합니다. 이것도 캐시하면 빌드 속도가 더 빨라집니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Step 2와 Step 3 사이에 추가&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Cache Gatsby build
  &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/cache@v4
  &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
      .cache
      public&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gatsby&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;build&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hashFiles(&apos;package&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lock.json&apos;) &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hashFiles(&apos;contents/&lt;span class=&quot;token important&quot;&gt;**&apos;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
      gatsby-build-${{ hashFiles(&apos;package-lock.json&apos;) }}-
      gatsby-build-&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;캐시 적용 전후 빌드 시간 비교&lt;/h3&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;npm install&lt;/td&gt;
&lt;td&gt;~60초&lt;/td&gt;
&lt;td&gt;~10초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gatsby build&lt;/td&gt;
&lt;td&gt;~120초&lt;/td&gt;
&lt;td&gt;~30초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체&lt;/td&gt;
&lt;td&gt;~3분&lt;/td&gt;
&lt;td&gt;~1분&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;실제 시간은 블로그 규모에 따라 다르지만, 캐시만으로 2~3배 빨라지는 것을 체감할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;캐시 포함 완성 workflow&lt;/h2&gt;
&lt;p&gt;캐시까지 포함한 최종 workflow 파일입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy Blog to GitHub Pages

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; main

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build-and-deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout repository
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup Node.js
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm&quot;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Cache Gatsby build
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/cache@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
            .cache
            public&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gatsby&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;build&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hashFiles(&apos;package&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lock.json&apos;) &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hashFiles(&apos;contents/&lt;span class=&quot;token important&quot;&gt;**&apos;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
            gatsby-build-${{ hashFiles(&apos;package-lock.json&apos;) }}-
            gatsby-build-&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install dependencies
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm ci

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build Gatsby site
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm run build
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;PREFIX_PATHS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy to GitHub Pages
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peaceiris/actions&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;github_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;publish_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./public
          &lt;span class=&quot;token key atrule&quot;&gt;publish_branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;커스텀 도메인 설정 (CNAME)&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;blog.javapark.kr&lt;/code&gt;처럼 커스텀 도메인을 사용하고 있다면, 배포할 때마다 CNAME 파일이 유지되어야 합니다.&lt;/p&gt;
&lt;h3&gt;방법 1: static 폴더에 CNAME 파일 추가&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;blog.javapark.kr&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; static/CNAME&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Gatsby 빌드 시 &lt;code class=&quot;language-text&quot;&gt;static/&lt;/code&gt; 폴더의 파일이 그대로 &lt;code class=&quot;language-text&quot;&gt;public/&lt;/code&gt;에 복사되므로, 배포 시 자동으로 포함됩니다.&lt;/p&gt;
&lt;h3&gt;방법 2: workflow에서 CNAME 옵션 사용&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy to GitHub Pages
  &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peaceiris/actions&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4
  &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;github_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;publish_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./public
    &lt;span class=&quot;token key atrule&quot;&gt;publish_branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gh&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages
    &lt;span class=&quot;token key atrule&quot;&gt;cname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog.javapark.kr&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;두 가지 방법 모두 동일한 결과를 만들지만, 저는 방법 1을 추천합니다. CNAME 파일이 프로젝트에 명시적으로 포함되어 있으면 관리가 더 편합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub Pages 설정 확인&lt;/h2&gt;
&lt;p&gt;workflow를 배포하기 전에 GitHub 레포지토리 설정을 확인해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub 레포지토리 &gt; &lt;strong&gt;Settings&lt;/strong&gt; 탭 클릭&lt;/li&gt;
&lt;li&gt;왼쪽 메뉴에서 &lt;strong&gt;Pages&lt;/strong&gt; 클릭&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt; 항목을 &lt;strong&gt;Deploy from a branch&lt;/strong&gt;로 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch&lt;/strong&gt;를 &lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;/ (root)&lt;/code&gt;로 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 설정이 되어 있어야 &lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; 브랜치에 push된 파일이 실제로 웹에 배포됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;배포 상태 확인 및 디버깅&lt;/h2&gt;
&lt;h3&gt;Actions 탭에서 확인&lt;/h3&gt;
&lt;p&gt;push 후 GitHub 레포지토리의 &lt;strong&gt;Actions&lt;/strong&gt; 탭에 들어가면 워크플로우 실행 상태를 확인할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;초록색 체크: 성공&lt;/li&gt;
&lt;li&gt;빨간색 X: 실패&lt;/li&gt;
&lt;li&gt;노란색 원: 진행 중&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;빌드 실패 시 확인할 것&lt;/h3&gt;
&lt;p&gt;실패한 워크플로우를 클릭하면 각 Step의 로그를 볼 수 있습니다. 자주 발생하는 오류와 해결법을 정리했습니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;npm ci&lt;/code&gt; 실패&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;package-lock.json&lt;/code&gt; 불일치&lt;/td&gt;
&lt;td&gt;로컬에서 &lt;code class=&quot;language-text&quot;&gt;npm install&lt;/code&gt; 후 lock 파일 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt; 실패&lt;/td&gt;
&lt;td&gt;플러그인 설정 오류&lt;/td&gt;
&lt;td&gt;로컬에서 &lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt; 먼저 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;권한 오류&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;permissions&lt;/code&gt; 미설정&lt;/td&gt;
&lt;td&gt;workflow에 &lt;code class=&quot;language-text&quot;&gt;permissions: contents: write&lt;/code&gt; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; 배포 안 됨&lt;/td&gt;
&lt;td&gt;Pages 설정 미완료&lt;/td&gt;
&lt;td&gt;Settings &gt; Pages에서 브랜치 설정 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;로컬에서 빌드 테스트&lt;/h3&gt;
&lt;p&gt;GitHub Actions에 push하기 전에 로컬에서 빌드가 정상인지 확인하는 습관을 들이면 좋습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 캐시 정리 후 빌드 테스트&lt;/span&gt;
gatsby clean &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; gatsby build&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;배포 프로세스 전체 흐름 정리&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;로컬에서 글 작성/수정
       |
       v
git add . &amp;amp;&amp;amp; git commit -m &quot;새 글 추가&quot;
       |
       v
git push origin main
       |
       v
GitHub Actions 자동 실행
  1. 코드 체크아웃
  2. Node.js 설정
  3. 캐시 복원
  4. npm ci
  5. gatsby build
  6. gh-pages 브랜치에 배포
       |
       v
GitHub Pages가 gh-pages 브랜치 감지
       |
       v
https://blog.javapark.kr 에 반영 완료&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 글을 쓰고 push만 하면 됩니다. 나머지는 전부 자동입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q. GitHub Actions는 무료인가요?&lt;/h3&gt;
&lt;p&gt;네, 공개(public) 레포지토리에서는 완전 무료입니다. 비공개(private) 레포지토리에서는 월 2,000분의 무료 빌드 시간이 제공됩니다. 블로그 하나를 운영하기에는 충분한 양입니다.&lt;/p&gt;
&lt;h3&gt;Q. main 브랜치가 아닌 다른 브랜치에서 배포하고 싶어요&lt;/h3&gt;
&lt;p&gt;workflow 파일의 &lt;code class=&quot;language-text&quot;&gt;on.push.branches&lt;/code&gt; 값을 변경하면 됩니다. 예를 들어 &lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt; 브랜치에서 배포하려면 다음과 같이 수정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; develop&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Q. 배포 후 사이트에 반영되기까지 얼마나 걸리나요?&lt;/h3&gt;
&lt;p&gt;보통 GitHub Actions 빌드에 1&lt;del&gt;3분, GitHub Pages 반영에 1&lt;/del&gt;2분 정도 걸립니다. 전체적으로 push 후 3~5분이면 사이트에 반영됩니다. 캐시가 따뜻한(warm) 상태라면 더 빨라집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 블로그 배포를 어떻게 하고 계신가요? 댓글로 알려주세요.&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;블로그에 광고와 댓글 시스템을 다는 방법&lt;/strong&gt;을 다룹니다. AdSense, 쿠팡 파트너스로 수익화하고, Utterances로 댓글 기능을 구축하는 방법까지 알아보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub 블로그 SEO 최적화 — 검색에 노출되는 블로그 만들기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "블로그 글을 열심히 썼는데, 구글에 검색해도 안 나와요." GitHub Pages…]]></description><link>https://blog.javapark.kr/04-seo-optimization/</link><guid isPermaLink="false">https://blog.javapark.kr/04-seo-optimization/</guid><pubDate>Sat, 04 Apr 2026 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;블로그 글을 열심히 썼는데, 구글에 검색해도 안 나와요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub Pages로 블로그를 만들고 글까지 발행했는데, 정작 검색 엔진에서 내 글이 보이지 않는다면 정말 허무하겠죠. 결론부터 말씀드리면 — 블로그를 만드는 것과 검색에 노출되는 것은 완전히 다른 문제입니다. SEO(Search Engine Optimization) 설정 없이는 아무리 좋은 글을 써도 구글이 찾아오지 않습니다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 Gatsby 기반 GitHub 블로그의 SEO를 처음부터 끝까지 최적화하는 방법을 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;SEO가 블로그에 중요한 이유&lt;/h2&gt;
&lt;p&gt;SEO란 검색 엔진 최적화(Search Engine Optimization)의 약자로, 구글이나 네이버 같은 검색 엔진이 내 블로그를 잘 이해하고, 검색 결과에 잘 노출시킬 수 있도록 만드는 작업입니다.&lt;/p&gt;
&lt;p&gt;블로그에 SEO가 중요한 이유는 간단합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;SEO 미적용&lt;/th&gt;
&lt;th&gt;SEO 적용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;검색 노출&lt;/td&gt;
&lt;td&gt;거의 없음&lt;/td&gt;
&lt;td&gt;키워드별 상위 노출 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유입 경로&lt;/td&gt;
&lt;td&gt;직접 공유만 가능&lt;/td&gt;
&lt;td&gt;구글 검색 유입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공유 시 미리보기&lt;/td&gt;
&lt;td&gt;제목만 표시&lt;/td&gt;
&lt;td&gt;이미지 + 설명 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;크롤링&lt;/td&gt;
&lt;td&gt;구조 파악 어려움&lt;/td&gt;
&lt;td&gt;사이트맵으로 효율적 크롤링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;특히 GitHub Pages 블로그는 티스토리나 워드프레스와 달리 SEO 설정이 자동으로 되지 않기 때문에 직접 설정해줘야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Gatsby SEO 컴포넌트 만들기 (React Helmet)&lt;/h2&gt;
&lt;p&gt;Gatsby에서 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;head&gt;&lt;/code&gt; 태그를 제어하려면 &lt;code class=&quot;language-text&quot;&gt;react-helmet&lt;/code&gt; 또는 Gatsby의 &lt;code class=&quot;language-text&quot;&gt;Head API&lt;/code&gt;를 사용합니다. gatsby-starter-hoodie 테마에는 이미 SEO 컴포넌트가 포함되어 있지만, 직접 만드는 방법도 알아두면 좋습니다.&lt;/p&gt;
&lt;h3&gt;react-helmet 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gatsby-plugin-react-helmet react-helmet&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby-config.js&lt;/code&gt;에 플러그인을 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-plugin-react-helmet&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... 기존 플러그인들&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;SEO 컴포넌트 기본 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/SEO.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Helmet &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react-helmet&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useStaticQuery&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; graphql &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;SEO&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; article &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; site &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useStaticQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;graphql&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
    query {
      site {
        siteMetadata {
          defaultTitle: title
          defaultDescription: description
          siteUrl
          defaultImage: image
        }
      }
    }
  &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; defaultTitle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultDescription&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; siteUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultImage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;siteMetadata

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; seo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; defaultTitle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; description &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; defaultDescription&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;siteUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;image &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; defaultImage&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;siteUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;

      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* Open Graph */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:url&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:type&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;article &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;article&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;website&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;

      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* Twitter Card */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:card&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;summary_large_image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SEO&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 컴포넌트를 각 페이지나 템플릿에서 불러와 사용하면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;메타태그 최적화 — title, description, keywords&lt;/h2&gt;
&lt;p&gt;메타태그는 검색 엔진이 페이지의 내용을 파악하는 핵심 정보입니다.&lt;/p&gt;
&lt;h3&gt;title 태그 작성법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;60자 이내로 작성합니다&lt;/li&gt;
&lt;li&gt;핵심 키워드를 앞쪽에 배치합니다&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;페이지 제목 | 사이트명&lt;/code&gt; 형식이 일반적입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;GitHub 블로그 SEO 최적화 가이드 | AI JAVAPARK&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;description 메타태그&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;150자 내외로 작성합니다&lt;/li&gt;
&lt;li&gt;페이지 내용을 요약하되, 클릭을 유도하는 문장으로 구성합니다&lt;/li&gt;
&lt;li&gt;검색 결과에서 제목 아래에 표시되는 텍스트입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;메타태그, sitemap, Open Graph 설정까지 — Gatsby 기반 GitHub 블로그의 검색 노출을 높이는 실전 SEO 가이드입니다.&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;gatsby-config.js의 siteMetadata 활용&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;siteMetadata&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;AI JAVAPARK&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;AI와 개발 이야기를 나누는 블로그&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;siteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://blog.javapark.kr&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/og-default.png&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;author&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;자바파커&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;각 포스팅의 frontmatter에 작성한 &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt;이 메타태그로 자동 연결되도록 템플릿을 구성하는 것이 좋습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Open Graph와 Twitter Card 설정&lt;/h2&gt;
&lt;p&gt;Open Graph(OG)는 페이스북, 카카오톡 등에서 링크 공유 시 미리보기를 결정하는 프로토콜입니다. Twitter Card는 트위터(현 X)에서의 미리보기입니다.&lt;/p&gt;
&lt;h3&gt;Open Graph 필수 태그&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;GitHub 블로그 SEO 최적화&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;검색 노출을 높이는 실전 가이드&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://blog.javapark.kr/og-image.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:url&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://blog.javapark.kr/github-blog/04-seo-optimization/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:type&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;article&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:site_name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;AI JAVAPARK&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Twitter Card 태그&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:card&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;summary_large_image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;GitHub 블로그 SEO 최적화&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:description&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;검색 노출을 높이는 실전 가이드&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;twitter:image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://blog.javapark.kr/og-image.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;OG 이미지 권장 사양&lt;/h3&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;크기&lt;/td&gt;
&lt;td&gt;1200 x 630px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포맷&lt;/td&gt;
&lt;td&gt;PNG 또는 JPG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 크기&lt;/td&gt;
&lt;td&gt;1MB 이하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;위치&lt;/td&gt;
&lt;td&gt;static 폴더에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;OG 이미지가 잘 적용됐는지 확인하려면 &lt;a href=&quot;https://developers.facebook.com/tools/debug/&quot;&gt;Facebook Sharing Debugger&lt;/a&gt;나 &lt;a href=&quot;https://cards-dev.twitter.com/validator&quot;&gt;Twitter Card Validator&lt;/a&gt;를 활용하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;sitemap.xml 생성하기&lt;/h2&gt;
&lt;p&gt;사이트맵은 검색 엔진에게 &quot;내 블로그에는 이런 페이지들이 있습니다&quot;라고 알려주는 XML 파일입니다. 일종의 블로그 목차라고 생각하면 됩니다.&lt;/p&gt;
&lt;h3&gt;gatsby-plugin-sitemap 설치 및 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gatsby-plugin-sitemap&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;siteMetadata&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;siteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://blog.javapark.kr&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-plugin-sitemap&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
          {
            allSitePage {
              nodes {
                path
              }
            }
          }
        &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;resolveSiteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://blog.javapark.kr&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;changefreq&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;weekly&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;빌드 후 &lt;code class=&quot;language-text&quot;&gt;public/sitemap-index.xml&lt;/code&gt;에 사이트맵이 생성됩니다. &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/sitemap-index.xml&lt;/code&gt;로 접근해서 확인할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;robots.txt 설정하기&lt;/h2&gt;
&lt;p&gt;robots.txt는 검색 엔진 크롤러에게 &quot;이 페이지는 크롤링해도 됩니다 / 하지 마세요&quot;를 알려주는 파일입니다.&lt;/p&gt;
&lt;h3&gt;gatsby-plugin-robots-txt 설치 및 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gatsby-plugin-robots-txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-plugin-robots-txt&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://blog.javapark.kr&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;sitemap&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://blog.javapark.kr/sitemap-index.xml&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;policy&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;userAgent&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;빌드 후 생성되는 &lt;code class=&quot;language-text&quot;&gt;robots.txt&lt;/code&gt; 내용은 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;User-agent: *
Allow: /

Sitemap: https://blog.javapark.kr/sitemap-index.xml
Host: https://blog.javapark.kr&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;Google Search Console 등록&lt;/h2&gt;
&lt;p&gt;사이트맵과 robots.txt를 만들었으면, 이제 구글에 &quot;내 블로그가 여기 있습니다&quot;라고 직접 알려줘야 합니다.&lt;/p&gt;
&lt;h3&gt;등록 절차&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://search.google.com/search-console&quot;&gt;Google Search Console&lt;/a&gt;에 접속합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;속성 추가&lt;/strong&gt; 버튼을 클릭합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL 접두어&lt;/strong&gt; 방식으로 &lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr&lt;/code&gt;을 입력합니다&lt;/li&gt;
&lt;li&gt;소유권 확인 방법 중 &lt;strong&gt;HTML 태그&lt;/strong&gt; 방식을 선택합니다&lt;/li&gt;
&lt;li&gt;제공되는 메타태그를 SEO 컴포넌트에 추가합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// SEO 컴포넌트에 추가&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;google-site-verification&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;여기에_구글이_제공한_인증코드&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;빌드 후 배포하고, Search Console에서 &lt;strong&gt;확인&lt;/strong&gt; 버튼을 누릅니다&lt;/li&gt;
&lt;li&gt;소유권 확인이 완료되면 &lt;strong&gt;Sitemaps&lt;/strong&gt; 메뉴에서 사이트맵 URL을 제출합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://blog.javapark.kr/sitemap-index.xml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;네이버 서치어드바이저 등록&lt;/h3&gt;
&lt;p&gt;네이버 검색 노출도 원한다면 &lt;a href=&quot;https://searchadvisor.naver.com/&quot;&gt;네이버 서치어드바이저&lt;/a&gt;에도 동일하게 등록합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;naver-site-verification&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;네이버_인증코드&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;구조화 데이터 (JSON-LD) 추가하기&lt;/h2&gt;
&lt;p&gt;구조화 데이터(Structured Data)는 검색 엔진에게 페이지의 의미를 더 정확하게 알려주는 방법입니다. 구글 검색 결과에서 별점, 작성일, 작성자 같은 리치 스니펫(Rich Snippet)이 표시되는 게 바로 구조화 데이터 덕분입니다.&lt;/p&gt;
&lt;h3&gt;블로그 포스팅용 JSON-LD&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/SEO.js 에 추가&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; structuredData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;@context&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.org&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;BlogPosting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;headline&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;datePublished&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;dateModified&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; update &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;author&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Person&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;자바파커&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://blog.javapark.kr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Organization&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AI JAVAPARK&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;logo&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string-property property&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ImageObject&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://blog.javapark.kr/logo.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;mainEntityOfPage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WebPage&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;@id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; seo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Helmet을 통해 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script&gt;&lt;/code&gt; 태그로 삽입합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;application/ld+json&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;{JSON.stringify(structuredData)}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Helmet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;구조화 데이터 테스트&lt;/h3&gt;
&lt;p&gt;구글의 &lt;a href=&quot;https://search.google.com/test/rich-results&quot;&gt;리치 결과 테스트&lt;/a&gt; 도구에서 URL을 입력하면 구조화 데이터가 제대로 적용됐는지 확인할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GitHub 블로그 SEO 검색 노출 체크리스트&lt;/h2&gt;
&lt;p&gt;모든 설정을 마쳤다면, 아래 체크리스트로 빠진 항목이 없는지 확인해보세요.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;code class=&quot;language-text&quot;&gt;gatsby-config.js&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;siteMetadata&lt;/code&gt;에 title, description, siteUrl, image 설정&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; SEO 컴포넌트에 title, description 메타태그 포함&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Open Graph 태그 (og:title, og:description, og:image, og:url) 설정&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Twitter Card 태그 설정&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;code class=&quot;language-text&quot;&gt;gatsby-plugin-sitemap&lt;/code&gt;으로 sitemap.xml 생성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;code class=&quot;language-text&quot;&gt;gatsby-plugin-robots-txt&lt;/code&gt;로 robots.txt 생성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Google Search Console에 사이트 등록 및 사이트맵 제출&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 네이버 서치어드바이저 등록 (선택)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 구조화 데이터 (JSON-LD) 추가&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; OG 이미지 (1200x630px) 준비&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 각 포스팅의 frontmatter에 title, description 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q. SEO 설정 후 검색에 바로 노출되나요?&lt;/h3&gt;
&lt;p&gt;아닙니다. 구글이 크롤링하고 인덱싱하는 데에는 며칠에서 몇 주까지 걸릴 수 있습니다. Search Console에서 직접 URL 색인 요청을 하면 속도를 앞당길 수 있습니다. Search Console의 &lt;strong&gt;URL 검사&lt;/strong&gt; 메뉴에서 URL을 입력한 뒤 &lt;strong&gt;색인 생성 요청&lt;/strong&gt; 버튼을 누르면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q. gatsby-starter-hoodie 테마에 이미 SEO가 적용되어 있나요?&lt;/h3&gt;
&lt;p&gt;네, 기본적인 메타태그와 OG 태그가 포함되어 있습니다. 다만 sitemap, robots.txt, 구조화 데이터, Search Console 등록은 직접 해야 합니다. 테마에 포함된 SEO 컴포넌트를 열어서 현재 어디까지 설정되어 있는지 확인해보시는 걸 추천합니다.&lt;/p&gt;
&lt;h3&gt;Q. OG 이미지를 포스팅마다 다르게 설정할 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, frontmatter에 &lt;code class=&quot;language-text&quot;&gt;image&lt;/code&gt; 필드를 추가하고, SEO 컴포넌트에서 해당 값을 읽어오도록 구성하면 됩니다. 포스팅별로 고유한 OG 이미지를 설정하면 SNS 공유 시 클릭률이 높아집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 블로그 SEO 어디까지 설정하고 계신가요? 댓글로 알려주세요.&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;GitHub Actions로 블로그를 자동 배포하는 방법&lt;/strong&gt;을 다룹니다. push만 하면 빌드부터 배포까지 알아서 되는 환경을 만들어보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[마크다운 글쓰기와 카테고리 관리 — 체계적인 블로그 운영법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 지금까지 Gatsby 블로그를 설치하고, 스킨을 커스터마이징하는 방법을 다뤘습니다. 이번 포스팅에서는 실제 글을 작성하고 콘텐츠를 체계적으로 관리하는 방법을 알아보겠습니다. 마크다운 문법부터 frontmatter…]]></description><link>https://blog.javapark.kr/03-markdown-and-categories/</link><guid isPermaLink="false">https://blog.javapark.kr/03-markdown-and-categories/</guid><pubDate>Sat, 04 Apr 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;지금까지 Gatsby 블로그를 설치하고, 스킨을 커스터마이징하는 방법을 다뤘습니다. 이번 포스팅에서는 실제 글을 작성하고 콘텐츠를 체계적으로 관리하는 방법을 알아보겠습니다. 마크다운 문법부터 frontmatter, 태그 전략, 시리즈 기능, 이미지 관리까지 블로그 운영에 필요한 모든 것을 다룹니다.&lt;/p&gt;
&lt;h2&gt;마크다운 기본 문법&lt;/h2&gt;
&lt;p&gt;마크다운은 텍스트 기반의 경량 마크업 언어입니다. 기술 블로그에서 가장 많이 사용되는 문법들을 정리합니다.&lt;/p&gt;
&lt;h3&gt;제목 (Heading)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;#&lt;/code&gt; 기호의 개수로 제목 수준을 구분합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; h1 — 포스트 제목 (frontmatter title과 중복되므로 본문에서는 사용하지 않습니다)&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; h2 — 주요 섹션&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; h3 — 하위 섹션&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;####&lt;/span&gt; h4 — 세부 항목&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: 본문에서는 &lt;code class=&quot;language-text&quot;&gt;##&lt;/code&gt;(h2)부터 시작하는 것이 SEO와 문서 구조에 좋습니다. h1은 포스트 제목이 자동으로 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;텍스트 서식&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;굵게&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt; 또는 &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;굵게&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token italic&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;기울임&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;/span&gt; 또는 &lt;span class=&quot;token italic&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;기울임&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;_&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token strike&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;~~&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;취소선&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;~~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token code-snippet code keyword&quot;&gt;`인라인 코드`&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;결과:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;굵게&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;기울임&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;취소선&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;인라인 코드&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;목록&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;순서 없는 목록:

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 항목 1
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 항목 2
  &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 중첩 항목 2-1
  &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 중첩 항목 2-2

순서 있는 목록:

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; 첫 번째
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; 두 번째
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; 세 번째&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;코드 블록&lt;/h3&gt;
&lt;p&gt;기술 블로그의 핵심 요소입니다. 백틱 세 개(&lt;code class=&quot;language-text&quot;&gt;```&lt;/code&gt;)로 감싸고 언어를 지정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;javascript&lt;/span&gt;
&lt;span class=&quot;token code-block language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; greeting &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;안녕하세요!&quot;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;greeting&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;python&lt;/span&gt;
&lt;span class=&quot;token code-block language-python&quot;&gt;def hello():
    print(&quot;안녕하세요!&quot;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;bash&lt;/span&gt;
&lt;span class=&quot;token code-block language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gatsby-cli &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;지원되는 주요 언어 키워드: &lt;code class=&quot;language-text&quot;&gt;javascript&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;typescript&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;java&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;bash&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;css&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;html&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;json&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;yaml&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sql&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;go&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;rust&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;kotlin&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;swift&lt;/code&gt; 등&lt;/p&gt;
&lt;h3&gt;표 (Table)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token table&quot;&gt;&lt;span class=&quot;token table-header-row&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 기능        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 설명        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 필수 여부 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token table-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-----------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-----------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;---------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token table-data-rows&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; title       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 포스트 제목 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 필수      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; description &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 짧은 설명   &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 권장      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; date        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 작성일      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 필수      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; tags        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 태그 목록   &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 권장      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;결과:&lt;/p&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;title&lt;/td&gt;
&lt;td&gt;포스트 제목&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;description&lt;/td&gt;
&lt;td&gt;짧은 설명&lt;/td&gt;
&lt;td&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date&lt;/td&gt;
&lt;td&gt;작성일&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tags&lt;/td&gt;
&lt;td&gt;태그 목록&lt;/td&gt;
&lt;td&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;인용 (Blockquote)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; 이것은 인용문입니다.
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; 여러 줄로 작성할 수 있습니다.

&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;참고&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;: 강조 표시와 함께 사용하면 주의사항이나 팁을 효과적으로 전달할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;: 강조 표시와 함께 사용하면 주의사항이나 팁을 효과적으로 전달할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;링크와 이미지&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;링크 텍스트&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://example.com&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;새 탭에서 열기&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://example.com&lt;/span&gt;)&lt;/span&gt;{:target=&quot;\_blank&quot;}

&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;이미지 대체 텍스트&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./images/example.png&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;외부 이미지&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://example.com/image.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;구분선&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token hr punctuation&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;세 개 이상의 하이픈(&lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt;), 별표(&lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt;), 밑줄(&lt;code class=&quot;language-text&quot;&gt;_&lt;/code&gt;)로 구분선을 만들 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Frontmatter 구조&lt;/h2&gt;
&lt;p&gt;Frontmatter는 마크다운 파일 상단에 YAML 형식으로 작성하는 메타데이터입니다. gatsby-starter-hoodie에서 지원하는 필드를 자세히 알아봅시다.&lt;/p&gt;
&lt;h3&gt;기본 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;포스트 제목&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;포스트에 대한 짧은 설명 (SEO 메타 설명에 사용됩니다)&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; gatsby
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; blog
&lt;span class=&quot;token key atrule&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;시리즈 이름&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;각 필드 상세 설명&lt;/h3&gt;
&lt;h4&gt;title (필수)&lt;/h4&gt;
&lt;p&gt;포스트의 제목입니다. HTML &lt;code class=&quot;language-text&quot;&gt;&amp;lt;title&gt;&lt;/code&gt; 태그와 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;h1&gt;&lt;/code&gt; 태그에 사용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Gatsby로 GitHub 블로그 시작하기 — 설치부터 첫 배포까지&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;큰따옴표로 감싸는 것이 안전합니다. 특히 콜론(&lt;code class=&quot;language-text&quot;&gt;:&lt;/code&gt;)이 포함된 경우 반드시 따옴표가 필요합니다.&lt;/li&gt;
&lt;li&gt;SEO를 위해 핵심 키워드를 앞쪽에 배치합니다.&lt;/li&gt;
&lt;li&gt;50~60자 이내가 검색 결과에서 잘리지 않아 적절합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;description (권장)&lt;/h4&gt;
&lt;p&gt;포스트에 대한 짧은 설명으로, 검색엔진 결과와 블로그 목록에 표시됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Gatsby 설치부터 gatsby-starter-hoodie 테마 적용, GitHub Pages 배포까지 완전 가이드&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;120~160자 이내가 적절합니다.&lt;/li&gt;
&lt;li&gt;핵심 키워드를 자연스럽게 포함합니다.&lt;/li&gt;
&lt;li&gt;클릭을 유도하는 명확한 문장을 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;date / update&lt;/h4&gt;
&lt;p&gt;작성일과 수정일입니다. &lt;code class=&quot;language-text&quot;&gt;YYYY-MM-DD&lt;/code&gt; 형식으로 작성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;date&lt;/code&gt;: 포스트 최초 작성일입니다. 목록 정렬에 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt;: 포스트 수정일입니다. 포스트를 업데이트했을 때 갱신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;tags&lt;/h4&gt;
&lt;p&gt;포스트에 붙이는 태그 목록입니다. 배열 형식으로 작성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; gatsby
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; blog&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;series&lt;/h4&gt;
&lt;p&gt;여러 포스트를 하나의 시리즈로 묶을 때 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GitHub 블로그 A to Z&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;같은 &lt;code class=&quot;language-text&quot;&gt;series&lt;/code&gt; 값을 가진 포스트들이 자동으로 그룹핑되어 포스트 하단에 시리즈 네비게이션이 표시됩니다.&lt;/p&gt;
&lt;h2&gt;폴더 구조 설계&lt;/h2&gt;
&lt;p&gt;블로그가 커지면 폴더 구조가 매우 중요해집니다. 처음부터 체계적으로 설계하면 나중에 관리가 편합니다.&lt;/p&gt;
&lt;h3&gt;추천 폴더 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;contents/
└── posts/
    ├── gatsby-blog/                     # 카테고리: Gatsby 블로그
    │   ├── 01-getting-started/
    │   │   ├── index.md
    │   │   └── images/
    │   │       ├── setup-screen.png
    │   │       └── deploy-result.png
    │   ├── 02-customizing-skin/
    │   │   └── index.md
    │   └── 03-markdown-and-categories/
    │       └── index.md
    ├── javascript/                      # 카테고리: JavaScript
    │   ├── closure-explained/
    │   │   └── index.md
    │   └── async-await-guide/
    │       └── index.md
    ├── devops/                          # 카테고리: DevOps
    │   ├── docker-basics/
    │   │   └── index.md
    │   └── github-actions-ci/
    │       └── index.md
    └── til/                             # 카테고리: Today I Learned
        ├── 2026-03-28-css-grid/
        │   └── index.md
        └── 2026-04-01-git-rebase/
            └── index.md&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;네이밍 규칙&lt;/h3&gt;
&lt;p&gt;폴더와 파일 이름에 일관된 규칙을 적용합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;카테고리 폴더&lt;/strong&gt;: 소문자 영문, 하이픈(&lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt;) 구분 (예: &lt;code class=&quot;language-text&quot;&gt;gatsby-blog&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;javascript&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포스트 폴더&lt;/strong&gt;: 시리즈의 경우 번호 접두사 사용 (예: &lt;code class=&quot;language-text&quot;&gt;01-getting-started&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단독 포스트&lt;/strong&gt;: 핵심 키워드로 구성 (예: &lt;code class=&quot;language-text&quot;&gt;closure-explained&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIL&lt;/strong&gt;: 날짜 접두사 사용 (예: &lt;code class=&quot;language-text&quot;&gt;2026-03-28-css-grid&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이미지 폴더&lt;/strong&gt;: 포스트 폴더 내 &lt;code class=&quot;language-text&quot;&gt;images/&lt;/code&gt; 하위에 관리&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;왜 index.md를 사용하는가?&lt;/h3&gt;
&lt;p&gt;포스트마다 폴더를 만들고 &lt;code class=&quot;language-text&quot;&gt;index.md&lt;/code&gt;를 사용하는 이유가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 이렇게 하면
posts/my-post.md          # 이미지를 어디에 둘지 애매합니다

# 이렇게 하면
posts/my-post/index.md    # 관련 이미지를 같은 폴더에 둘 수 있습니다
posts/my-post/images/screenshot.png&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지를 같은 폴더에 두면 상대 경로(&lt;code class=&quot;language-text&quot;&gt;./images/screenshot.png&lt;/code&gt;)로 참조할 수 있어 관리가 훨씬 편합니다.&lt;/p&gt;
&lt;h2&gt;태그 전략&lt;/h2&gt;
&lt;p&gt;태그를 잘 설계하면 독자가 관련 글을 쉽게 찾을 수 있고, SEO에도 도움이 됩니다.&lt;/p&gt;
&lt;h3&gt;태그 작성 규칙&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;소문자 통일&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;JavaScript&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;javascript&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;react&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하이픈 구분&lt;/strong&gt;: 두 단어 이상인 경우 하이픈으로 연결합니다. (예: &lt;code class=&quot;language-text&quot;&gt;github-pages&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;styled-components&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;복수형 지양&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;algorithms&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;algorithm&lt;/code&gt;처럼 단수형을 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의미 있는 태그&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;기타&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;잡담&lt;/code&gt; 같은 모호한 태그는 피합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;적정 개수&lt;/strong&gt;: 포스트당 3~5개가 적절합니다. 너무 많으면 태그의 의미가 희석됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;태그 분류 체계&lt;/h3&gt;
&lt;p&gt;태그를 기술/도구/개념/시리즈로 분류하면 체계적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 기술/언어 태그&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;javascript&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; typescript&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; python&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; java&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; go&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 프레임워크/도구 태그&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;gatsby&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; react&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next.js&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; docker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 개념 태그&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;algorithm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;structure&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; design&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clean&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 유형 태그&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tutorial&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; review&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; til&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;태그 관리 스프레드시트&lt;/h3&gt;
&lt;p&gt;블로그가 커지면 태그 목록을 별도로 관리하는 것이 좋습니다. 간단한 표를 만들어 사용 중인 태그와 포스트 수를 추적합니다.&lt;/p&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;gatsby&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;블로그 시리즈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;javascript&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;주력 언어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;프론트엔드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;til&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Today I Learned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;시리즈 기능 활용&lt;/h2&gt;
&lt;p&gt;gatsby-starter-hoodie의 시리즈 기능은 연관된 포스트를 그룹으로 묶어주는 강력한 기능입니다.&lt;/p&gt;
&lt;h3&gt;시리즈 설정 방법&lt;/h3&gt;
&lt;p&gt;frontmatter에 같은 &lt;code class=&quot;language-text&quot;&gt;series&lt;/code&gt; 값을 지정하면 자동으로 시리즈가 생성됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 포스트 1&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Gatsby 블로그 시작하기&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GitHub 블로그 A to Z&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-01&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 포스트 2&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;블로그 스킨 커스터마이징&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GitHub 블로그 A to Z&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 포스트 3&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;마크다운 글쓰기와 카테고리 관리&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GitHub 블로그 A to Z&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-07&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&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;ul&gt;
&lt;li&gt;시리즈 이름은 &lt;strong&gt;한국어&lt;/strong&gt;로 작성해도 됩니다. 태그와 달리 시리즈 이름은 URL에 직접 사용되지 않습니다.&lt;/li&gt;
&lt;li&gt;명확하고 구체적인 이름을 사용합니다. (예: &quot;GitHub 블로그 A to Z&quot;, &quot;React 핵심 개념 정리&quot;)&lt;/li&gt;
&lt;li&gt;시리즈 내 포스트 순서는 &lt;code class=&quot;language-text&quot;&gt;date&lt;/code&gt; 값으로 결정됩니다. 날짜를 순차적으로 설정하면 원하는 순서대로 표시됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시리즈 활용 사례&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;시리즈 1: &quot;GitHub 블로그 A to Z&quot;
├── 01. Gatsby로 GitHub 블로그 시작하기
├── 02. 블로그 스킨 커스터마이징
├── 03. 마크다운 글쓰기와 카테고리 관리
├── 04. SEO 최적화
├── 05. GitHub Actions 자동 배포
└── 06. 광고 및 댓글 시스템

시리즈 2: &quot;JavaScript 핵심 개념&quot;
├── 01. 실행 컨텍스트와 호이스팅
├── 02. 클로저 완벽 이해
├── 03. 프로토타입과 상속
└── 04. 비동기 프로그래밍&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;이미지 관리&lt;/h2&gt;
&lt;p&gt;기술 블로그에서 이미지는 스크린샷, 다이어그램, 아키텍처 도표 등으로 자주 사용됩니다. 효율적인 이미지 관리 방법을 알아봅시다.&lt;/p&gt;
&lt;h3&gt;로컬 이미지&lt;/h3&gt;
&lt;p&gt;가장 간단한 방법은 포스트 폴더 내에 이미지를 함께 보관하는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;posts/my-post/
├── index.md
└── images/
    ├── screenshot-01.png
    └── architecture-diagram.png&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;마크다운에서 상대 경로로 참조합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;스크린샷&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./images/screenshot-01.png&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;아키텍처 다이어그램&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./images/architecture-diagram.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 포스트와 이미지가 함께 관리되어 직관적입니다.
&lt;strong&gt;단점&lt;/strong&gt;: 리포지토리 용량이 커질 수 있습니다. GitHub 리포지토리의 권장 용량은 1GB 이하입니다.&lt;/p&gt;
&lt;h3&gt;GitHub Release 활용&lt;/h3&gt;
&lt;p&gt;리포지토리 용량을 아끼면서 이미지를 관리하는 방법입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub 리포지토리 &gt; &lt;strong&gt;Releases&lt;/strong&gt; &gt; &lt;strong&gt;Draft a new release&lt;/strong&gt;로 이동합니다.&lt;/li&gt;
&lt;li&gt;이미지 파일을 드래그 앤 드롭으로 업로드합니다.&lt;/li&gt;
&lt;li&gt;생성된 URL을 마크다운에서 사용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;스크린샷&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://github.com/username/repo/releases/download/v1.0/screenshot.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: GitHub Issues에 이미지를 드래그 앤 드롭하면 자동으로 &lt;code class=&quot;language-text&quot;&gt;user-images.githubusercontent.com&lt;/code&gt; URL이 생성됩니다. 이 URL을 복사해서 사용할 수도 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;외부 CDN 활용&lt;/h3&gt;
&lt;p&gt;이미지가 많은 블로그라면 별도의 CDN 서비스를 사용하는 것이 효율적입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Images&lt;/strong&gt;: 합리적인 가격, 이미지 리사이징 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;imgbb&lt;/strong&gt;: 무료 이미지 호스팅&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub 자체 CDN&lt;/strong&gt;: Issues/PR에 이미지를 업로드하면 생성되는 URL 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;이미지 최적화&lt;/h3&gt;
&lt;p&gt;이미지 크기가 크면 페이지 로딩 속도에 영향을 줍니다. 다음 사항을 지키면 좋습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;포맷&lt;/strong&gt;: 스크린샷은 PNG, 사진은 WebP 또는 JPEG를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;크기&lt;/strong&gt;: 콘텐츠 폭의 2배(Retina 대응) 정도가 적절합니다. 본문 폭이 760px이면 이미지 폭은 1520px 이하로 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;압축&lt;/strong&gt;: &lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh&lt;/a&gt;나 &lt;a href=&quot;https://tinypng.com/&quot;&gt;TinyPNG&lt;/a&gt;로 압축합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;alt 텍스트&lt;/strong&gt;: 접근성과 SEO를 위해 항상 의미 있는 대체 텍스트를 작성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 좋은 예 --&gt;&lt;/span&gt;

&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;Gatsby 프로젝트 폴더 구조&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./images/folder-structure.png&lt;/span&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 나쁜 예 --&gt;&lt;/span&gt;

![](./images/image1.png)
&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;사진&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./images/photo.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;글쓰기 워크플로우&lt;/h2&gt;
&lt;p&gt;마지막으로, 실제 글쓰기 워크플로우를 정리합니다.&lt;/p&gt;
&lt;h3&gt;새 포스트 작성 과정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1. 포스트 폴더 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; contents/posts/category-name/post-title

&lt;span class=&quot;token comment&quot;&gt;# 2. index.md 파일 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; contents/posts/category-name/post-title/index.md

&lt;span class=&quot;token comment&quot;&gt;# 3. frontmatter 작성&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 4. 본문 작성&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 5. 개발 서버에서 확인&lt;/span&gt;
gatsby develop

&lt;span class=&quot;token comment&quot;&gt;# 6. 문제가 없으면 커밋 및 배포&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: add new post - 포스트 제목&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run deploy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;포스트 템플릿&lt;/h3&gt;
&lt;p&gt;매번 frontmatter를 처음부터 작성하는 것은 번거롭습니다. 아래 템플릿을 복사해서 사용하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: &quot;&quot;
description: &quot;&quot;
date: 2026-04-04
update: 2026-04-04
tags:
  -
series: &quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

안녕하세요, &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;자바파커&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;입니다.

(도입부: 이 글에서 다루는 내용과 독자가 얻을 수 있는 것)

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 섹션 1&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; 하위 섹션&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 섹션 2&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 자주 묻는 질문 (FAQ)&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; Q1.&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; Q2.&lt;/span&gt;

&lt;span class=&quot;token hr punctuation&quot;&gt;---&lt;/span&gt;

다음 포스팅에서는 ~ 다룹니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. 마크다운 에디터는 무엇을 추천하나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt;를 가장 추천합니다. 마크다운 프리뷰(&lt;code class=&quot;language-text&quot;&gt;Ctrl+Shift+V&lt;/code&gt;), 마크다운 린트, 이미지 붙여넣기 등 블로그 글쓰기에 필요한 확장 기능이 풍부합니다. 추천 확장 프로그램은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Markdown All in One&lt;/strong&gt;: 단축키, 목차 생성, 서식 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown Preview Enhanced&lt;/strong&gt;: 향상된 미리보기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paste Image&lt;/strong&gt;: 클립보드 이미지를 바로 붙여넣기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;markdownlint&lt;/strong&gt;: 마크다운 문법 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Q2. 태그와 시리즈, 카테고리의 차이점은 무엇인가요?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;태그(tag)&lt;/strong&gt;: 포스트의 주제나 기술을 나타내는 키워드입니다. 하나의 포스트에 여러 태그를 붙일 수 있습니다. (예: &lt;code class=&quot;language-text&quot;&gt;javascript&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;react&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;tutorial&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시리즈(series)&lt;/strong&gt;: 순서대로 읽어야 하는 연속 포스트를 그룹핑합니다. 하나의 포스트는 하나의 시리즈에만 속할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;카테고리&lt;/strong&gt;: gatsby-starter-hoodie에서는 폴더 구조로 카테고리를 대체합니다. 별도의 카테고리 frontmatter 필드는 없으며, 폴더 경로가 곧 카테고리 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Q3. 이전에 작성한 글을 수정하면 date도 바꿔야 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;date&lt;/code&gt;는 최초 작성일이므로 변경하지 않습니다. 대신 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 필드를 수정일로 갱신합니다. 이렇게 하면 목록 정렬 순서는 유지되면서 독자에게 &quot;최근 업데이트 됨&quot;을 알릴 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-04&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 최초 작성일 (변경하지 않음)&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2026-04-10&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 수정일 (갱신)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;SEO 최적화&lt;/strong&gt; 방법을 다룹니다. 검색엔진에 블로그가 잘 노출되도록 메타 태그, 사이트맵, 구글 서치 콘솔 등록까지 실전 가이드를 알아보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Gatsby 블로그 스킨 커스터마이징 — 나만의 디자인 만들기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 지난 포스팅에서 Gatsby 블로그를 설치하고 배포까지 마쳤습니다. 하지만 기본 테마 그대로 사용하기에는 아쉬운 부분이 있습니다. 이번 포스팅에서는 gatsby-starter-hoodie…]]></description><link>https://blog.javapark.kr/02-customizing-skin/</link><guid isPermaLink="false">https://blog.javapark.kr/02-customizing-skin/</guid><pubDate>Sat, 04 Apr 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;지난 포스팅에서 Gatsby 블로그를 설치하고 배포까지 마쳤습니다. 하지만 기본 테마 그대로 사용하기에는 아쉬운 부분이 있습니다. 이번 포스팅에서는 gatsby-starter-hoodie 테마를 기준으로 블로그 디자인을 나만의 스타일로 커스터마이징하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;Gatsby 테마 구조 이해&lt;/h2&gt;
&lt;p&gt;커스터마이징을 시작하기 전에 테마의 폴더 구조를 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── assets/
│   └── theme/
│       └── index.js        # 테마 색상, 크기 등 핵심 설정
├── components/
│   ├── Article/            # 본문 영역 컴포넌트
│   ├── Bio/                # 프로필 영역
│   ├── Header/             # 상단 헤더
│   ├── Layout/             # 전체 레이아웃
│   ├── Sidebar/            # 사이드바
│   └── ...
├── pages/
│   └── index.js            # 메인 페이지
└── templates/
    └── Post.js             # 개별 포스트 템플릿&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심 파일은 &lt;code class=&quot;language-text&quot;&gt;src/assets/theme/index.js&lt;/code&gt;입니다. 이 파일에서 색상, 크기, 폰트 등 대부분의 디자인 변수를 관리합니다.&lt;/p&gt;
&lt;h2&gt;콘텐츠 폭 조정&lt;/h2&gt;
&lt;p&gt;기본 테마의 콘텐츠 폭이 좁게 느껴진다면 조정할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Layout/Body 컴포넌트 수정&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;src/components/Layout/Body/index.js&lt;/code&gt; 파일을 찾아 &lt;code class=&quot;language-text&quot;&gt;max-width&lt;/code&gt; 값을 변경합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/Layout/Body/index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; styled &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;styled-components&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;div&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  margin: 0 auto;
  padding-top: 80px;
  max-width: 680px; /* 기본값 */

  @media (max-width: 768px) {
    padding: 80px 16px 0;
  }
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 값을 필요에 따라 조정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;div&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  margin: 0 auto;
  padding-top: 80px;
  max-width: 760px; /* 더 넓은 콘텐츠 영역 */

  @media (max-width: 768px) {
    padding: 80px 20px 0;
  }
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: 너무 넓으면 한 줄에 글자 수가 많아져 가독성이 떨어집니다. 일반적으로 680px~800px 사이가 적당합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;테마 변수로 관리하기&lt;/h3&gt;
&lt;p&gt;반복 사용되는 값은 테마 변수로 관리하면 유지보수가 편리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/assets/theme/index.js 에 추가&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;maxWidth&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;760px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;mobileBreakpoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;768px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tabletBreakpoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1024px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; size &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;타이포그래피 변경&lt;/h2&gt;
&lt;p&gt;블로그의 분위기를 결정짓는 가장 큰 요소는 폰트입니다.&lt;/p&gt;
&lt;h3&gt;웹 폰트 추가&lt;/h3&gt;
&lt;p&gt;Google Fonts나 눈누(Noonnu)에서 원하는 폰트를 선택합니다. &lt;code class=&quot;language-text&quot;&gt;gatsby-browser.js&lt;/code&gt;에서 웹 폰트를 로드하는 방법이 가장 간단합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-browser.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./src/assets/fonts/fonts.css&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* src/assets/fonts/fonts.css */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string url&quot;&gt;&quot;https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&amp;amp;display=swap&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string url&quot;&gt;&quot;https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&amp;amp;display=swap&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;폰트 적용&lt;/h3&gt;
&lt;p&gt;전역 스타일 또는 테마 파일에서 폰트를 적용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/assets/theme/index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fonts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&apos;Noto Sans KR&apos;, -apple-system, BlinkMacSystemFont, &apos;Segoe UI&apos;, sans-serif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&apos;JetBrains Mono&apos;, &apos;Fira Code&apos;, Consolas, monospace&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fonts &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;styled-components에서 사용할 때는 다음과 같이 적용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fonts &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../../assets/theme&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;div&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  font-family: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;fonts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  font-size: 16px;
  line-height: 1.8;
  letter-spacing: -0.01em;
  word-break: keep-all;
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;글꼴 크기와 행간 조정&lt;/h3&gt;
&lt;p&gt;가독성을 높이려면 &lt;strong&gt;line-height&lt;/strong&gt;(행간)와 &lt;strong&gt;letter-spacing&lt;/strong&gt;(자간) 조정이 중요합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* 추천 설정 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 16px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* 본문 기본 크기 */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.8&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* 행간: 1.6~1.8 추천 */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;letter-spacing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -0.01em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* 한글은 약간 좁히면 자연스럽습니다 */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;word-break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; keep-all&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* 한글 단어 단위 줄바꿈 */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 700&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 700&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;h3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.25rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 500&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;다크/라이트 테마 색상 커스터마이징&lt;/h2&gt;
&lt;p&gt;gatsby-starter-hoodie는 다크모드를 기본 지원합니다. 색상 설정은 테마 파일에서 관리합니다.&lt;/p&gt;
&lt;h3&gt;theme/index.js 수정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/assets/theme/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; light &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 배경 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;bodyBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ffffff&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cardBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#f8f9fa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 텍스트 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#1a1a2e&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;textSecondary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#6c757d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;textTertiary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#adb5bd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 강조 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;primary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#0066cc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;primaryLight&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#e7f1ff&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 경계선&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#e9ecef&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;borderLight&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#f1f3f5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 인라인 코드&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;inlineCodeBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#f1f3f5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;inlineCodeText&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#e74c3c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 헤더&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;headerBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rgba(255, 255, 255, 0.85)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dark &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 배경 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;bodyBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#1a1a2e&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cardBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#16213e&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 텍스트 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#e0e0e0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;textSecondary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#a0a0a0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;textTertiary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#707070&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 강조 색상&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;primary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#4dabf7&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;primaryLight&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#1a2744&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 경계선&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#2c3e50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;borderLight&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#233554&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 인라인 코드&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;inlineCodeBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#2d2d2d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;inlineCodeText&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ff6b6b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 헤더&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;headerBackground&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rgba(26, 26, 46, 0.85)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; light&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dark &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;색상 팔레트 선택 팁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;배경색&lt;/strong&gt;: 순백(&lt;code class=&quot;language-text&quot;&gt;#fff&lt;/code&gt;)보다 살짝 톤을 줄인 &lt;code class=&quot;language-text&quot;&gt;#fafafa&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;#f8f9fa&lt;/code&gt;가 눈에 편합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다크모드 배경&lt;/strong&gt;: 순검정(&lt;code class=&quot;language-text&quot;&gt;#000&lt;/code&gt;)보다 짙은 남색(&lt;code class=&quot;language-text&quot;&gt;#1a1a2e&lt;/code&gt;)이나 짙은 회색(&lt;code class=&quot;language-text&quot;&gt;#1e1e1e&lt;/code&gt;)이 부드럽습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;강조색(primary)&lt;/strong&gt;: 블로그의 정체성을 나타내는 색상을 하나 정해서 일관되게 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대비&lt;/strong&gt;: 텍스트와 배경의 명암비가 최소 4.5:1 이상이 되어야 가독성이 보장됩니다. &lt;a href=&quot;https://webaim.org/resources/contrastchecker/&quot;&gt;WebAIM Contrast Checker&lt;/a&gt;에서 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;코드 블록 스타일링&lt;/h2&gt;
&lt;p&gt;기술 블로그에서 코드 블록은 매우 중요한 요소입니다. Gatsby는 기본적으로 &lt;strong&gt;Prism.js&lt;/strong&gt;를 사용합니다.&lt;/p&gt;
&lt;h3&gt;Prism.js 테마 변경&lt;/h3&gt;
&lt;p&gt;gatsby-starter-hoodie에서 사용하는 Prism 테마를 변경하려면 &lt;code class=&quot;language-text&quot;&gt;gatsby-browser.js&lt;/code&gt;에서 import를 수정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-browser.js&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 기본 테마 대신 원하는 테마를 import합니다&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// import &quot;prismjs/themes/prism.css&quot;           // 라이트&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// import &quot;prismjs/themes/prism-tomorrow.css&quot;  // 다크&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// import &quot;prismjs/themes/prism-okaidia.css&quot;   // Monokai 스타일&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;prismjs/themes/prism-vsc-dark-plus.css&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// VS Code 스타일&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;커스텀 코드 블록 스타일&lt;/h3&gt;
&lt;p&gt;Prism 테마를 직접 오버라이드하면 더 세밀하게 조정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* src/assets/styles/code.css */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 코드 블록 컨테이너 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.gatsby-highlight&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5em 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 코드 블록 본문 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.gatsby-highlight pre[class*=&quot;language-&quot;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #1e1e2e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.2em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 14px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.6&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;overflow-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;-webkit-overflow-scrolling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; touch&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 인라인 코드 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;:not(pre) &gt; code[class*=&quot;language-&quot;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--inline-code-bg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #f1f3f5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--inline-code-text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #e74c3c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.15em 0.4em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.9em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 코드 블록 언어 표시 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.gatsby-highlight pre[class*=&quot;language-&quot;]::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2px 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 11px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #999&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;text-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; uppercase&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;코드 복사 버튼 추가&lt;/h3&gt;
&lt;p&gt;코드 블록에 복사 버튼을 추가하면 사용자 경험이 크게 향상됩니다. &lt;code class=&quot;language-text&quot;&gt;gatsby-remark-code-buttons&lt;/code&gt; 플러그인을 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gatsby-remark-code-buttons&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby-config.js&lt;/code&gt;에 플러그인을 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby-transformer-remark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby-remark-code-buttons&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token literal-property property&quot;&gt;buttonText&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;복사&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token literal-property property&quot;&gt;toasterText&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;복사되었습니다!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token literal-property property&quot;&gt;toasterDuration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// ... 기존 플러그인들&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;복사 버튼의 스타일도 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* 코드 복사 버튼 스타일 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.gatsby-code-button-container&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.gatsby-code-button&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #ccc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 12px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pointer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; all 0.2s ease&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.gatsby-code-button:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #fff&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;헤더/푸터 커스터마이징&lt;/h2&gt;
&lt;h3&gt;헤더 수정&lt;/h3&gt;
&lt;p&gt;헤더는 블로그의 첫인상을 결정합니다. &lt;code class=&quot;language-text&quot;&gt;src/components/Header/index.js&lt;/code&gt;를 수정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/Header/index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; styled &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;styled-components&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; HeaderWrapper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;header&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 56px;
  background: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headerBackground&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;border&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  transition: background 0.3s ease;
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Logo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  font-size: 1.2rem;
  font-weight: 700;
  color: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  text-decoration: none;

  &amp;amp;:hover {
    color: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;primary&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  }
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;backdrop-filter: blur()&lt;/code&gt;를 사용하면 스크롤할 때 배경이 흐려지는 글래스모피즘 효과를 줄 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;푸터 추가&lt;/h3&gt;
&lt;p&gt;gatsby-starter-hoodie에는 기본 푸터가 간단하게 되어 있습니다. 커스텀 푸터를 추가해 봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/Footer/index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; styled &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;styled-components&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; FooterWrapper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;footer&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  margin-top: 80px;
  padding: 40px 0;
  border-top: 1px solid &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;border&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
  text-align: center;
  font-size: 0.85rem;
  color: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textSecondary&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Footer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;FooterWrapper&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;copy&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFullYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JAVAPARK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; Built &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;a href&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://www.gatsbyjs.com/&quot;&lt;/span&gt; target&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;_blank&quot;&lt;/span&gt; rel&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        Gatsby
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;FooterWrapper&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Footer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;반응형 브레이크포인트 조정&lt;/h2&gt;
&lt;p&gt;다양한 디바이스에서 최적의 경험을 제공하기 위해 반응형 디자인을 조정합니다.&lt;/p&gt;
&lt;h3&gt;미디어 쿼리 정리&lt;/h3&gt;
&lt;p&gt;일관된 미디어 쿼리를 사용하기 위해 브레이크포인트를 정의합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/assets/theme/breakpoints.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; breakpoints &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;mobile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;576px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tablet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;768px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;desktop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1024px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;wide&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1200px&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; media &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;mobile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@media (max-width: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;breakpoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mobile&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tablet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@media (max-width: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;breakpoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tablet&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;desktop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@media (max-width: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;breakpoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;desktop&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;wide&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@media (min-width: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;breakpoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wide&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; breakpoints&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; media &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;styled-components에서 활용&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; media &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../../assets/theme/breakpoints&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;div&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  max-width: 760px;
  margin: 0 auto;
  padding: 0 24px;

  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tablet&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; {
    padding: 0 20px;
  }

  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mobile&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; {
    padding: 0 16px;
  }
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;모바일 최적화 체크리스트&lt;/h3&gt;
&lt;p&gt;반응형 커스터마이징 시 다음 항목들을 꼭 확인합니다.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 모바일에서 텍스트가 잘리지 않는지 확인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 코드 블록이 가로 스크롤로 처리되는지 확인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 이미지가 컨테이너 폭을 넘지 않는지 확인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 터치 타겟(버튼, 링크)이 최소 44px 이상인지 확인&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 헤더가 모바일에서 적절히 축소되는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;크롬 개발자 도구(&lt;code class=&quot;language-text&quot;&gt;F12&lt;/code&gt;)의 디바이스 툴바를 활용하면 다양한 화면 크기를 쉽게 테스트할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;변경사항 적용 및 배포&lt;/h2&gt;
&lt;p&gt;모든 커스터마이징을 마쳤다면 개발 서버에서 결과를 확인하고 배포합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 개발 서버에서 확인&lt;/span&gt;
gatsby develop

&lt;span class=&quot;token comment&quot;&gt;# 문제가 없다면 배포&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run deploy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;주의&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;gatsby develop&lt;/code&gt;에서는 보이지 않는 문제가 &lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt;에서 발생할 수 있습니다. 배포 전에 반드시 &lt;code class=&quot;language-text&quot;&gt;gatsby build&lt;/code&gt;로 프로덕션 빌드를 테스트하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. styled-components를 몰라도 커스터마이징할 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, 기본 CSS 문법만 알면 됩니다. styled-components는 JavaScript 파일 안에 CSS를 작성하는 방식일 뿐, 문법 자체는 CSS와 동일합니다. 백틱(&lt;code class=&quot;language-text&quot;&gt;`&lt;/code&gt;) 안에 일반 CSS를 그대로 작성하면 됩니다. 다만 &lt;code class=&quot;language-text&quot;&gt;${}&lt;/code&gt; 구문으로 JavaScript 변수를 활용할 수 있다는 점이 일반 CSS와 다릅니다.&lt;/p&gt;
&lt;h3&gt;Q2. 테마 수정 후 기존 글 스타일이 깨지는 경우 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby clean&lt;/code&gt; 명령어로 캐시를 초기화한 뒤 다시 빌드해 보세요. 대부분의 스타일 관련 이슈는 캐시 문제입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;gatsby clean &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; gatsby develop&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그래도 문제가 지속된다면 브라우저 개발자 도구에서 해당 요소를 검사하여 어떤 스타일이 적용되고 있는지 확인합니다.&lt;/p&gt;
&lt;h3&gt;Q3. 다크모드 전환이 깜빡이는 현상이 있습니다.&lt;/h3&gt;
&lt;p&gt;SSR(서버 사이드 렌더링) 시점과 클라이언트 사이드 렌더링 시점의 테마가 다르면 깜빡임이 발생합니다. &lt;code class=&quot;language-text&quot;&gt;gatsby-ssr.js&lt;/code&gt;에서 초기 테마를 설정하는 스크립트를 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;body&gt;&lt;/code&gt; 태그 앞에 삽입하면 해결됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-ssr.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onRenderBody&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; setPreBodyComponents &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setPreBodyComponents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script
      key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;theme-script&quot;&lt;/span&gt;
      dangerouslySetInnerHTML&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;__html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
          (function() {
            try {
              var mode = localStorage.getItem(&apos;theme&apos;);
              if (mode) {
                document.body.className = mode;
              }
            } catch (e) {}
          })();
        &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;마크다운 글쓰기와 카테고리 관리&lt;/strong&gt; 방법을 다룹니다. frontmatter 작성법, 태그 전략, 시리즈 기능, 폴더 구조 설계까지 체계적인 블로그 운영법을 알아보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Gatsby로 GitHub 블로그 시작하기 — 설치부터 첫 배포까지]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 개발자라면 한 번쯤 나만의 기술 블로그를 운영해보고 싶다는 생각을 해보셨을 겁니다. 네이버 블로그, 티스토리, 벨로그 등 다양한 플랫폼이 있지만, 저는 GitHub Pages + Gatsby…]]></description><link>https://blog.javapark.kr/01-getting-started/</link><guid isPermaLink="false">https://blog.javapark.kr/01-getting-started/</guid><pubDate>Sat, 04 Apr 2026 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;개발자라면 한 번쯤 나만의 기술 블로그를 운영해보고 싶다는 생각을 해보셨을 겁니다. 네이버 블로그, 티스토리, 벨로그 등 다양한 플랫폼이 있지만, 저는 &lt;strong&gt;GitHub Pages + Gatsby&lt;/strong&gt; 조합을 강력히 추천합니다.&lt;/p&gt;
&lt;p&gt;이 포스팅에서는 Gatsby와 gatsby-starter-hoodie 테마를 활용해 GitHub 블로그를 처음부터 배포까지 완성하는 전 과정을 다룹니다.&lt;/p&gt;
&lt;h2&gt;왜 GitHub Pages 블로그인가?&lt;/h2&gt;
&lt;p&gt;GitHub Pages 블로그를 선택해야 하는 이유는 명확합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;비용 0원&lt;/strong&gt;: 호스팅 비용이 전혀 들지 않습니다. GitHub에서 무료로 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발자 친화적&lt;/strong&gt;: Git으로 버전 관리를 하고, 마크다운으로 글을 씁니다. 개발자에게 이보다 자연스러운 워크플로우는 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마크다운 기반&lt;/strong&gt;: 코드 블록, 수식, 표 등을 편리하게 작성할 수 있습니다. 기술 블로그에 최적화된 형식입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스터마이징 자유&lt;/strong&gt;: HTML, CSS, JavaScript를 완전히 제어할 수 있습니다. 원하는 기능을 직접 추가하고, 디자인을 자유롭게 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 도메인 지원&lt;/strong&gt;: 무료 HTTPS와 함께 나만의 도메인을 연결할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO 유리&lt;/strong&gt;: 정적 사이트 생성(SSG) 방식이라 검색엔진 최적화에 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;다른 플랫폼과 비교&lt;/h2&gt;
&lt;p&gt;블로그 플랫폼을 선택할 때 가장 많이 고민하는 옵션들을 비교해 보겠습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;GitHub Pages&lt;/th&gt;
&lt;th&gt;티스토리&lt;/th&gt;
&lt;th&gt;벨로그&lt;/th&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;비용&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;일부 유료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커스터마이징&lt;/td&gt;
&lt;td&gt;완전 자유&lt;/td&gt;
&lt;td&gt;스킨 수준&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;거의 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마크다운&lt;/td&gt;
&lt;td&gt;네이티브 지원&lt;/td&gt;
&lt;td&gt;부분 지원&lt;/td&gt;
&lt;td&gt;네이티브 지원&lt;/td&gt;
&lt;td&gt;부분 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 블록&lt;/td&gt;
&lt;td&gt;커스텀 가능&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO 제어&lt;/td&gt;
&lt;td&gt;완전 제어&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;광고 수익&lt;/td&gt;
&lt;td&gt;직접 설정&lt;/td&gt;
&lt;td&gt;카카오 애드핏&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;Partner Program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;초기 설정 난이도&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;매우 낮음&lt;/td&gt;
&lt;td&gt;매우 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;초기 설정이 다소 복잡하다는 단점이 있지만, 이 시리즈를 따라오시면 어렵지 않게 완성할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;사전 준비&lt;/h2&gt;
&lt;p&gt;Gatsby 블로그를 시작하기 전에 다음 도구들이 필요합니다.&lt;/p&gt;
&lt;h3&gt;Node.js 설치&lt;/h3&gt;
&lt;p&gt;Gatsby는 Node.js 기반 프레임워크입니다. &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js 공식 사이트&lt;/a&gt;에서 &lt;strong&gt;LTS 버전&lt;/strong&gt;을 설치합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 설치 확인&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# v20.x.x 이상 권장&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 10.x.x 이상&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: nvm(Node Version Manager)을 사용하면 Node.js 버전을 쉽게 관리할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Git 설치&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git 공식 사이트&lt;/a&gt;에서 설치하거나, 아래 명령어로 확인합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# git version 2.x.x&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;GitHub 계정 생성&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;에 계정이 없다면 가입합니다. GitHub Pages를 사용하려면 GitHub 계정이 필수입니다.&lt;/p&gt;
&lt;h2&gt;Gatsby 설치 및 프로젝트 생성&lt;/h2&gt;
&lt;h3&gt;Gatsby CLI 설치&lt;/h3&gt;
&lt;p&gt;먼저 Gatsby CLI를 전역으로 설치합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; gatsby-cli&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;gatsby-starter-hoodie로 프로젝트 생성&lt;/h3&gt;
&lt;p&gt;gatsby-starter-hoodie는 깔끔한 디자인과 다크모드, 시리즈 기능 등을 기본 제공하는 테마입니다. 이 블로그(AI JAVAPARK)에서도 사용하고 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 프로젝트 생성&lt;/span&gt;
npx gatsby new my-blog https://github.com/devHudi/gatsby-starter-hoodie

&lt;span class=&quot;token comment&quot;&gt;# 프로젝트 폴더로 이동&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-blog&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로젝트 생성이 완료되면 다음과 같은 폴더 구조가 생성됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;my-blog/
├── contents/
│   └── posts/          # 블로그 글 저장 폴더
├── src/
│   ├── assets/         # 테마, 이미지 등 정적 자원
│   ├── components/     # React 컴포넌트
│   ├── pages/          # 페이지 컴포넌트
│   └── templates/      # 템플릿 컴포넌트
├── static/             # 정적 파일 (favicon 등)
├── blog-config.js      # 블로그 설정 파일
├── gatsby-config.js    # Gatsby 설정 파일
├── gatsby-node.js      # Gatsby Node API
└── package.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;blog-config.js 설정&lt;/h2&gt;
&lt;p&gt;프로젝트 루트에 있는 &lt;code class=&quot;language-text&quot;&gt;blog-config.js&lt;/code&gt; 파일을 열어 블로그 기본 정보를 설정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AI JAVAPARK&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;개발과 AI를 다루는 기술 블로그&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;author&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;자바파커&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;siteUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://blog.javapark.kr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/javapark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 사용하지 않는 링크는 빈 문자열로 두면 됩니다&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;linkedIn&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;facebook&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;instagram&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mailto:your-email@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;utterances&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;javapark/blog-comments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pathname&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;각 항목을 설명하면 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;title&lt;/strong&gt;: 블로그 제목입니다. 브라우저 탭과 헤더에 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;description&lt;/strong&gt;: 블로그 설명입니다. SEO 메타 태그에 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;author&lt;/strong&gt;: 작성자 이름입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;siteUrl&lt;/strong&gt;: 배포될 블로그 URL입니다. GitHub Pages 기본 URL(&lt;code class=&quot;language-text&quot;&gt;https://username.github.io&lt;/code&gt;) 또는 커스텀 도메인을 입력합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;links&lt;/strong&gt;: SNS 링크입니다. 프로필 영역에 아이콘으로 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;utterances&lt;/strong&gt;: GitHub Issues 기반 댓글 시스템 설정입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;로컬 개발 서버 실행&lt;/h2&gt;
&lt;p&gt;설정을 마쳤다면 로컬에서 블로그를 확인해 봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 개발 서버 실행&lt;/span&gt;
gatsby develop&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;브라우저에서 &lt;code class=&quot;language-text&quot;&gt;http://localhost:8000&lt;/code&gt;으로 접속하면 블로그를 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 만약 포트 충돌이 발생한다면 포트를 변경할 수 있습니다&lt;/span&gt;
gatsby develop &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&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;빌드 테스트&lt;/h3&gt;
&lt;p&gt;배포 전에 프로덕션 빌드가 정상적으로 되는지 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 프로덕션 빌드&lt;/span&gt;
gatsby build

&lt;span class=&quot;token comment&quot;&gt;# 빌드 결과 로컬에서 확인&lt;/span&gt;
gatsby serve&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatsby serve&lt;/code&gt;를 실행하면 &lt;code class=&quot;language-text&quot;&gt;http://localhost:9000&lt;/code&gt;에서 빌드된 결과물을 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;GitHub Pages 배포&lt;/h2&gt;
&lt;p&gt;이제 가장 중요한 단계인 배포를 진행합니다.&lt;/p&gt;
&lt;h3&gt;GitHub 리포지토리 생성&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;GitHub에서 &lt;strong&gt;New repository&lt;/strong&gt;를 클릭합니다.&lt;/li&gt;
&lt;li&gt;Repository name을 &lt;code class=&quot;language-text&quot;&gt;username.github.io&lt;/code&gt; 형식으로 입력합니다. (예: &lt;code class=&quot;language-text&quot;&gt;javapark.github.io&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public&lt;/strong&gt;으로 설정합니다. (GitHub Pages 무료 플랜은 Public 리포지토리에서만 동작합니다.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create repository&lt;/strong&gt;를 클릭합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;원격 리포지토리 연결&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 기존 원격 저장소 제거 (starter 원본이 연결되어 있을 수 있습니다)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote remove origin

&lt;span class=&quot;token comment&quot;&gt;# 새 리포지토리 연결&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; origin https://github.com/username/username.github.io.git

&lt;span class=&quot;token comment&quot;&gt;# 기본 브랜치를 main으로 설정&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-M&lt;/span&gt; main

&lt;span class=&quot;token comment&quot;&gt;# 첫 커밋 및 푸시&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Initial commit&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token parameter variable&quot;&gt;-u&lt;/span&gt; origin main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;gh-pages 패키지로 배포&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt; 패키지를 사용하면 빌드 결과물만 별도 브랜치에 배포할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# gh-pages 패키지 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; gh-pages --save-dev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;에 배포 스크립트를 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;deploy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby build &amp;amp;&amp;amp; gh-pages -d public -b gh-pages&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;배포를 실행합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run deploy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;GitHub Pages 설정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;GitHub 리포지토리 &gt; &lt;strong&gt;Settings&lt;/strong&gt; &gt; &lt;strong&gt;Pages&lt;/strong&gt;로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt;를 &lt;code class=&quot;language-text&quot;&gt;Deploy from a branch&lt;/code&gt;로 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch&lt;/strong&gt;를 &lt;code class=&quot;language-text&quot;&gt;gh-pages&lt;/code&gt;로 선택하고 폴더는 &lt;code class=&quot;language-text&quot;&gt;/ (root)&lt;/code&gt;로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt;를 클릭합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;몇 분 뒤 &lt;code class=&quot;language-text&quot;&gt;https://username.github.io&lt;/code&gt;에 블로그가 배포됩니다.&lt;/p&gt;
&lt;h3&gt;배포 확인&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 배포 상태 확인 (GitHub CLI가 설치되어 있다면)&lt;/span&gt;
gh api repos/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;owner&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;repo&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/pages &lt;span class=&quot;token parameter variable&quot;&gt;--jq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;.status&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;브라우저에서 &lt;code class=&quot;language-text&quot;&gt;https://username.github.io&lt;/code&gt;에 접속하여 블로그가 정상적으로 표시되는지 확인합니다.&lt;/p&gt;
&lt;h2&gt;첫 글 작성해보기&lt;/h2&gt;
&lt;p&gt;배포가 완료되었으니 첫 글을 작성해 봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 글 폴더 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; contents/posts/my-first-post&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;contents/posts/my-first-post/index.md&lt;/code&gt; 파일을 만들고 다음과 같이 작성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: &quot;나의 첫 블로그 포스팅&quot;
description: &quot;GitHub 블로그에 첫 글을 작성했습니다.&quot;
date: 2026-04-04
update: 2026-04-04
tags:
  - 블로그
  - 시작&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 안녕하세요!&lt;/span&gt;

이것은 나의 첫 블로그 포스팅입니다.

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; 코드 블록도 됩니다&lt;/span&gt;

\```javascript
console.log(&quot;Hello, Blog!&quot;);
\```&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;글을 작성한 뒤 다시 배포합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run deploy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. GitHub Pages는 정말 무료인가요?&lt;/h3&gt;
&lt;p&gt;네, 완전히 무료입니다. Public 리포지토리에서는 GitHub Pages를 무료로 사용할 수 있으며, 월 트래픽 제한은 약 100GB입니다. 일반적인 개인 블로그에서는 이 제한에 도달할 일이 거의 없습니다.&lt;/p&gt;
&lt;h3&gt;Q2. gatsby-starter-hoodie 대신 다른 테마를 써도 되나요?&lt;/h3&gt;
&lt;p&gt;물론입니다. &lt;a href=&quot;https://www.gatsbyjs.com/starters/&quot;&gt;Gatsby Starter Library&lt;/a&gt;에서 다양한 테마를 확인할 수 있습니다. 다만 이 시리즈에서는 gatsby-starter-hoodie를 기준으로 설명하므로, 다른 테마를 선택하면 설정 방법이 다를 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q3. 커스텀 도메인은 어떻게 연결하나요?&lt;/h3&gt;
&lt;p&gt;GitHub 리포지토리 Settings &gt; Pages에서 &lt;strong&gt;Custom domain&lt;/strong&gt; 항목에 원하는 도메인을 입력합니다. 그 전에 도메인 DNS 설정에서 CNAME 레코드를 &lt;code class=&quot;language-text&quot;&gt;username.github.io&lt;/code&gt;로 지정해야 합니다. 이 부분은 시리즈 후반부에서 자세히 다룰 예정입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 &lt;strong&gt;Gatsby 블로그 스킨 커스터마이징&lt;/strong&gt; 방법을 다룹니다. 콘텐츠 폭, 폰트, 색상, 다크모드까지 나만의 블로그 디자인을 만들어 보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[운영과 모니터링 — 출시 후 해야 할 것들]]></title><description><![CDATA[…]]></description><link>https://blog.javapark.kr/05-operation-monitoring/</link><guid isPermaLink="false">https://blog.javapark.kr/05-operation-monitoring/</guid><pubDate>Sat, 04 Apr 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;드디어 시리즈의 마지막 포스팅입니다. 기획부터 설계, 개발, 배포까지 해냈습니다. 축하합니다! 하지만 여기서 끝이 아닙니다. &lt;strong&gt;출시는 시작일 뿐입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;서비스를 세상에 내놓은 순간부터 진짜 게임이 시작됩니다. 사용자가 어떤 에러를 만나는지, 어떤 기능을 많이 쓰는지, 서비스가 얼마나 빠른지 -- 이 모든 것을 파악하고 개선해야 합니다. 이번 포스팅에서는 1인 개발자가 서비스를 안정적으로 운영하기 위해 꼭 해야 할 것들을 정리합니다.&lt;/p&gt;
&lt;h2&gt;출시는 시작일 뿐 -- 운영의 중요성&lt;/h2&gt;
&lt;p&gt;많은 1인 개발자들이 출시에만 집중하고, 이후 운영을 소홀히 합니다. 하지만 성공적인 서비스는 출시 이후의 운영에서 결정됩니다.&lt;/p&gt;
&lt;p&gt;운영 단계에서 해야 할 핵심 업무는 네 가지입니다.&lt;/p&gt;
&lt;ol&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;: 페이지 로딩 속도, API 응답 시간 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;피드백 수집&lt;/strong&gt;: 사용자의 목소리를 듣고 개선에 반영&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 네 가지를 모두 무료 도구로 구축하는 방법을 알려드리겠습니다.&lt;/p&gt;
&lt;h2&gt;에러 트래킹 -- Sentry 연동&lt;/h2&gt;
&lt;p&gt;서비스를 운영하다 보면 예상치 못한 에러가 반드시 발생합니다. 사용자가 에러를 직접 알려주기를 기대할 수 없습니다. &lt;strong&gt;Sentry&lt;/strong&gt;를 사용하면 에러가 발생하는 즉시 알림을 받을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Sentry 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Sentry Next.js SDK 설치&lt;/span&gt;
npx @sentry/wizard@latest &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; nextjs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 명령을 실행하면 Sentry 설정 마법사가 시작됩니다. Sentry 계정 로그인, 프로젝트 생성, 설정 파일 생성까지 자동으로 진행됩니다.&lt;/p&gt;
&lt;p&gt;마법사가 완료되면 아래 파일들이 자동 생성됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;sentry.client.config.ts   — 클라이언트 사이드 Sentry 설정
sentry.server.config.ts   — 서버 사이드 Sentry 설정
sentry.edge.config.ts     — Edge Runtime Sentry 설정
next.config.js             — Sentry 플러그인이 자동 추가됨
.env.sentry-build-plugin   — Sentry 인증 토큰&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Sentry 기본 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// sentry.client.config.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Sentry &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@sentry/nextjs&quot;&lt;/span&gt;

Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  dsn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SENTRY_DSN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 에러 샘플링 비율 (1.0 = 모든 에러 수집)&lt;/span&gt;
  tracesSampleRate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 프로덕션에서만 활성화&lt;/span&gt;
  enabled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 사용자 세션 리플레이 (에러 발생 전후 화면 녹화)&lt;/span&gt;
  replaysSessionSampleRate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 전체 세션의 10%&lt;/span&gt;
  replaysOnErrorSampleRate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 에러 발생 시 100%&lt;/span&gt;

  integrations&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replayIntegration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// sentry.server.config.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Sentry &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@sentry/nextjs&quot;&lt;/span&gt;

Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  dsn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SENTRY_DSN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  tracesSampleRate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  enabled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;커스텀 에러 캡처&lt;/h3&gt;
&lt;p&gt;자동 에러 수집 외에도 중요한 비즈니스 로직에서 직접 에러를 캡처할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// lib/api-client.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Sentry &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@sentry/nextjs&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchWithErrorTracking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestInit
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// HTTP 에러도 Sentry에 보고&lt;/span&gt;
      Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;API Error: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        level&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;warning&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        extra&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          statusText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statusText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 네트워크 에러 등 캡처&lt;/span&gt;
    Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      extra&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Sentry 알림 설정&lt;/h3&gt;
&lt;p&gt;에러가 발생하면 즉시 알림을 받도록 설정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Sentry 대시보드 → Settings → Alerts
→ &quot;Create Alert Rule&quot;
→ 조건: &quot;A new issue is created&quot;
→ 액션: 이메일 알림 또는 Slack 알림
→ 빈도: &quot;실시간&quot; 또는 &quot;10분마다 요약&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1인 개발자에게는 &lt;strong&gt;Slack 알림&lt;/strong&gt;을 추천합니다. 에러 발생 시 즉시 알림을 받을 수 있고, 에러 상세 정보로 바로 이동할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;사용자 분석 -- Google Analytics 4 연동&lt;/h2&gt;
&lt;p&gt;서비스에 사용자가 어떻게 들어오고, 어떤 페이지를 보고, 어디서 떠나는지 파악하는 것은 서비스 개선의 핵심입니다.&lt;/p&gt;
&lt;h3&gt;GA4 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Google Analytics 패키지 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @next/third-parties&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; GoogleAnalytics &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@next/third-parties/google&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RootLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReactNode
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;html lang&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ko&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;GoogleAnalytics gaId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_GA_ID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;커스텀 이벤트 트래킹&lt;/h3&gt;
&lt;p&gt;페이지 조회 외에도 사용자의 주요 행동을 추적할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// lib/analytics.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  eventName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  parameters&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;undefined&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gtag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eventName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 사용 예시&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 회원가입 완료&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sign_up&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// AI 기능 사용&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ai_generate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; feature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 유료 전환&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;purchase&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;KRW&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// components/GenerateButton.tsx&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;use client&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trackEvent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/analytics&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GenerateButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ai_generate_click&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; page&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dashboard&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// AI 생성 로직...&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ai_generate_complete&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; duration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2500&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt;로 생성하기&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;GA4에서 확인할 핵심 지표&lt;/h3&gt;
&lt;p&gt;1인 개발자가 꼭 봐야 할 지표입니다.&lt;/p&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;활성 사용자&lt;/td&gt;
&lt;td&gt;실제 서비스를 쓰는 사람 수&lt;/td&gt;
&lt;td&gt;홈 &gt; 개요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이벤트 수&lt;/td&gt;
&lt;td&gt;핵심 기능 사용 빈도&lt;/td&gt;
&lt;td&gt;참여도 &gt; 이벤트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이탈률&lt;/td&gt;
&lt;td&gt;첫 페이지에서 바로 떠나는 비율&lt;/td&gt;
&lt;td&gt;참여도 &gt; 페이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 획득&lt;/td&gt;
&lt;td&gt;어디서 유입되는지&lt;/td&gt;
&lt;td&gt;획득 &gt; 트래픽 소스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전환율&lt;/td&gt;
&lt;td&gt;핵심 목표 달성 비율&lt;/td&gt;
&lt;td&gt;구성 &gt; 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;성능 모니터링 -- Vercel Analytics와 Core Web Vitals&lt;/h2&gt;
&lt;p&gt;서비스가 느리면 사용자는 떠납니다. 성능을 지속적으로 모니터링하고 개선해야 합니다.&lt;/p&gt;
&lt;h3&gt;Vercel Analytics 활성화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Vercel Analytics 패키지 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @vercel/analytics&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Analytics &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@vercel/analytics/react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; SpeedInsights &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@vercel/speed-insights/next&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RootLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReactNode
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;html lang&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ko&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Analytics &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;SpeedInsights &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Core Web Vitals 관리&lt;/h3&gt;
&lt;p&gt;Google이 정한 웹 성능 핵심 지표입니다. SEO에도 영향을 미치므로 반드시 관리해야 합니다.&lt;/p&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;LCP (Largest Contentful Paint)&lt;/td&gt;
&lt;td&gt;가장 큰 콘텐츠가 보이기까지&lt;/td&gt;
&lt;td&gt;2.5초 이하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INP (Interaction to Next Paint)&lt;/td&gt;
&lt;td&gt;사용자 입력에 반응하기까지&lt;/td&gt;
&lt;td&gt;200ms 이하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS (Cumulative Layout Shift)&lt;/td&gt;
&lt;td&gt;레이아웃이 갑자기 움직이는 정도&lt;/td&gt;
&lt;td&gt;0.1 이하&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;성능 개선 체크리스트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 이미지 최적화 — next/image 사용&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Image &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/image&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HeroSection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Image
      src&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/hero.webp&quot;&lt;/span&gt;
      alt&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;히어로 이미지&quot;&lt;/span&gt;
      width&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      height&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;630&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      priority &lt;span class=&quot;token comment&quot;&gt;// LCP 대상 이미지에 priority 추가&lt;/span&gt;
      placeholder&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blur&quot;&lt;/span&gt;
      blurDataURL&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;data:image/jpeg;base64,...&quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 동적 임포트로 초기 로딩 최적화&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; dynamic &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/dynamic&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 무거운 컴포넌트는 필요할 때만 로드&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; HeavyChart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dynamic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@/components/HeavyChart&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;차트 로딩 중&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ssr&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;사용자 피드백 수집&lt;/h2&gt;
&lt;p&gt;데이터만으로는 알 수 없는 것들이 있습니다. 사용자의 직접적인 목소리를 듣는 것이 중요합니다.&lt;/p&gt;
&lt;h3&gt;간단한 피드백 폼 만들기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// components/FeedbackWidget.tsx&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;use client&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase/client&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FeedbackWidget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;isOpen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setIsOpen&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;feedback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setFeedback&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setType&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bug&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feature&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;general&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;general&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;submitted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setSubmitted&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleSubmit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FormEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;feedback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; feedback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      page&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      user_agent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userAgent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;setSubmitted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setIsOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setSubmitted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setFeedback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isOpen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
        onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setIsOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-full shadow-lg&quot;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        피드백 보내기
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixed bottom-4 right-4 w-80 bg-white rounded-lg shadow-xl p-4 border&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;submitted &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-center text-green-600 font-medium&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          소중한 피드백 감사합니다&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;form onSubmit&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleSubmit&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h3 className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;font-bold mb-2&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;피드백 보내기&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h3&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex gap-2 mb-3&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bug&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;general&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
                key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt;
                onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px-3 py-1 rounded text-sm &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;
                  type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-blue-600 text-white&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-gray-100&quot;&lt;/span&gt;
                &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bug&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;버그&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feature&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;기능 요청&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;일반&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;textarea
            value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;feedback&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;e &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setFeedback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            placeholder&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;의견을 자유롭게 작성해 주세요&quot;&lt;/span&gt;
            className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;w-full border rounded p-2 mb-3 h-24 resize-none&quot;&lt;/span&gt;
            required
          &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex justify-end gap-2&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
              type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt;
              onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setIsOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;px-3 py-1 text-gray-500&quot;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              취소
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
              type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;submit&quot;&lt;/span&gt;
              className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;px-3 py-1 bg-blue-600 text-white rounded&quot;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              보내기
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;form&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Supabase 피드백 테이블&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Supabase SQL Editor에서 실행&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; feedback &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;bug&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;feature&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;general&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  message &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  page &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_agent &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  is_resolved &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- RLS 정책 (누구나 피드백 작성 가능, 읽기는 관리자만)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; feedback &lt;span class=&quot;token keyword&quot;&gt;ENABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; SECURITY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Anyone can insert feedback&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; feedback &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;TO&lt;/span&gt; anon
  &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;GitHub Issues 활용&lt;/h3&gt;
&lt;p&gt;피드백 폼 외에 GitHub Issues를 활용하면 개발자 커뮤니티에서 피드백을 받기 좋습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;GitHub 저장소 → Settings → Features → Issues 활성화
→ Issue Template 생성:
  - Bug Report (버그 리포트)
  - Feature Request (기능 요청)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- .github/ISSUE_TEMPLATE/bug_report.md --&gt;&lt;/span&gt;

name: Bug Report
about: 버그를 신고해 주세요
title: &apos;[BUG] &apos;
labels: bug

&lt;span class=&quot;token hr punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;버그 설명&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
어떤 문제가 발생했나요?

&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;재현 방법&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; &apos;...&apos; 페이지에 접속
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; &apos;...&apos; 버튼 클릭
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; 에러 발생

&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;예상 동작&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
어떻게 동작해야 하나요?

&lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;스크린샷&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
가능하다면 스크린샷을 첨부해 주세요.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;업데이트 전략&lt;/h2&gt;
&lt;p&gt;서비스를 지속적으로 개선하려면 체계적인 업데이트 전략이 필요합니다.&lt;/p&gt;
&lt;h3&gt;기능 추가 우선순위 정하기&lt;/h3&gt;
&lt;p&gt;모든 피드백과 아이디어를 구현할 수는 없습니다. 우선순위를 정하는 간단한 프레임워크입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ICE 스코어링:
- Impact (영향력): 1~10 — 얼마나 많은 사용자에게 도움이 되는가?
- Confidence (확신도): 1~10 — 실제로 효과가 있을 확신이 있는가?
- Ease (난이도): 1~10 — 얼마나 쉽게 구현할 수 있는가?

ICE Score = Impact × Confidence × Ease
→ 점수가 높은 것부터 구현&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;th&gt;Ease&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;다크모드&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;504&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소셜 로그인&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 번역&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;버전 관리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Semantic Versioning 사용&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# MAJOR.MINOR.PATCH&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 버그 수정&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; version patch  &lt;span class=&quot;token comment&quot;&gt;# 1.0.0 → 1.0.1&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 새 기능 추가&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; version minor  &lt;span class=&quot;token comment&quot;&gt;# 1.0.1 → 1.1.0&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 호환되지 않는 변경&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; version major  &lt;span class=&quot;token comment&quot;&gt;# 1.1.0 → 2.0.0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;비용 관리 -- 무료 티어로 운영하는 전략&lt;/h2&gt;
&lt;p&gt;1인 개발자에게 비용 관리는 생존의 문제입니다. 다행히 2026년 현재 대부분의 서비스가 넉넉한 무료 티어를 제공합니다.&lt;/p&gt;
&lt;h3&gt;무료 티어 한도 정리&lt;/h3&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;Vercel (Hobby)&lt;/td&gt;
&lt;td&gt;100GB 대역폭, 무제한 배포&lt;/td&gt;
&lt;td&gt;Pro: $20/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase (Free)&lt;/td&gt;
&lt;td&gt;500MB DB, 1GB 스토리지, 50,000 MAU&lt;/td&gt;
&lt;td&gt;Pro: $25/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry (Developer)&lt;/td&gt;
&lt;td&gt;5,000 이벤트/월&lt;/td&gt;
&lt;td&gt;Team: $26/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA4&lt;/td&gt;
&lt;td&gt;무제한 (무료)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;무제한 공개 저장소&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI API&lt;/td&gt;
&lt;td&gt;종량제 (무료 한도 없음)&lt;/td&gt;
&lt;td&gt;GPT-4o: ~$5/1M 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;비용 절약 팁&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. API 호출 최소화 — 캐싱 활용&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unstable_cache &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/cache&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; getCachedData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unstable_cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 비용이 드는 API 호출&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchFromOpenAI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;openai-cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; revalidate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3600&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1시간 캐시&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 2. Supabase 쿼리 최적화 — 필요한 필드만 조회&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 나쁜 예&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;posts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 좋은 예&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;posts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id, title, created_at&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ascending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 3. 이미지 최적화 — Vercel Image Optimization 활용&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// next.config.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  images&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    formats&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/avif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    minimumCacheTTL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 30일 캐시&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;비용 모니터링&lt;/h3&gt;
&lt;p&gt;각 서비스의 사용량을 주기적으로 확인하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;매주 월요일 체크리스트:
[ ] Vercel 대시보드 → Usage 탭 확인
[ ] Supabase 대시보드 → Settings → Billing 확인
[ ] Sentry 대시보드 → Settings → Subscription 확인
[ ] OpenAI 대시보드 → Usage 확인&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;1인 개발자의 AI 스택 시리즈 마무리&lt;/h2&gt;
&lt;p&gt;5편에 걸친 시리즈를 통해 아이디어 검증부터 운영까지 전체 과정을 다뤘습니다. 처음에는 막막해 보였을 수도 있지만, 하나씩 따라오시면 충분히 할 수 있는 과정입니다.&lt;/p&gt;
&lt;h3&gt;시리즈 전체 요약 표&lt;/h3&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;th&gt;주요 도구&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1편&lt;/td&gt;
&lt;td&gt;아이디어 검증&lt;/td&gt;
&lt;td&gt;문제 정의, 시장 조사, MVP 범위 설정&lt;/td&gt;
&lt;td&gt;ChatGPT, Notion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2편&lt;/td&gt;
&lt;td&gt;AI 기반 설계&lt;/td&gt;
&lt;td&gt;UI/UX 디자인, DB 스키마, API 설계&lt;/td&gt;
&lt;td&gt;v0, Cursor, Supabase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3편&lt;/td&gt;
&lt;td&gt;풀스택 개발&lt;/td&gt;
&lt;td&gt;Next.js + Supabase로 MVP 구현&lt;/td&gt;
&lt;td&gt;Next.js, Supabase, Tailwind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4편&lt;/td&gt;
&lt;td&gt;배포 자동화&lt;/td&gt;
&lt;td&gt;Vercel + GitHub 원클릭 배포&lt;/td&gt;
&lt;td&gt;Vercel, GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5편&lt;/td&gt;
&lt;td&gt;운영과 모니터링&lt;/td&gt;
&lt;td&gt;에러 트래킹, 분석, 피드백, 비용 관리&lt;/td&gt;
&lt;td&gt;Sentry, GA4, Vercel Analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;이 시리즈에서 구축한 AI 스택 전체 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[사용자] → [Vercel Edge Network]
                    ↓
            [Next.js App Router]
           /        |         \
    [Server]   [Client]   [API Routes]
        ↓          ↓           ↓
  [Supabase]  [React UI]  [OpenAI API]
  (DB/Auth)   (Tailwind)  (AI 기능)
        ↓
  [Sentry]  [GA4]  [Vercel Analytics]
  (에러)    (분석)  (성능)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;다음 단계를 위한 제안&lt;/h3&gt;
&lt;p&gt;시리즈를 마쳤다면 다음 단계를 고려해 보세요.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;수익화&lt;/strong&gt;: Stripe 결제 연동으로 유료 기능 추가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마케팅&lt;/strong&gt;: Product Hunt 런칭, 기술 블로그 작성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장&lt;/strong&gt;: 모바일 앱 (React Native), 크롬 익스텐션&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커뮤니티&lt;/strong&gt;: Discord 서버 운영, 오픈소스 기여&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1인 개발자도 AI 도구의 도움을 받으면 기획부터 운영까지 혼자 해낼 수 있는 시대입니다. 이 시리즈가 여러분의 첫 서비스를 만드는 데 도움이 되었으면 합니다.&lt;/p&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. Sentry 무료 플랜으로 충분한가요?&lt;/h3&gt;
&lt;p&gt;대부분의 초기 서비스에는 충분합니다. Sentry Developer 플랜은 월 5,000개의 에러 이벤트를 처리할 수 있습니다. 일일 방문자가 수백 명 수준이라면 무료 한도를 넘기기 어렵습니다. 만약 한도에 가까워진다면 &lt;code class=&quot;language-text&quot;&gt;tracesSampleRate&lt;/code&gt;를 낮춰서 수집 비율을 조절하세요. 예를 들어 &lt;code class=&quot;language-text&quot;&gt;0.2&lt;/code&gt;로 설정하면 전체 트랜잭션의 20%만 수집합니다.&lt;/p&gt;
&lt;h3&gt;Q2. Google Analytics 대신 다른 분석 도구를 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, 프라이버시를 중시한다면 &lt;strong&gt;Plausible Analytics&lt;/strong&gt;나 &lt;strong&gt;Umami&lt;/strong&gt;를 추천합니다. 둘 다 오픈소스이고, 쿠키를 사용하지 않아서 쿠키 동의 배너가 필요 없습니다. Umami는 Vercel + Supabase에 셀프 호스팅할 수 있어서 비용도 들지 않습니다. 다만 GA4만큼 상세한 분석 기능은 제공하지 않으므로 서비스 규모에 맞게 선택하세요.&lt;/p&gt;
&lt;h3&gt;Q3. 서비스 사용자가 늘어서 무료 티어를 초과하면 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;축하할 일입니다! 사용자가 늘고 있다는 뜻이니까요. 보통 무료 티어를 초과하는 시점이면 이미 수익화를 고려할 단계입니다. 우선순위는 Supabase Pro ($25/월) &gt; Vercel Pro ($20/월) &gt; Sentry Team ($26/월) 순서로 업그레이드하는 것을 추천합니다. Supabase가 가장 먼저 한도에 도달하는 경우가 많기 때문입니다. 총 월 $71이면 안정적인 서비스 운영이 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이것으로 &quot;&lt;strong&gt;1인 개발자의 AI 스택&lt;/strong&gt;&quot; 시리즈를 마칩니다. 기획에서 운영까지, 아이디어를 현실로 만드는 전체 여정을 함께 했습니다. 이 시리즈가 여러분의 서비스 개발에 실질적인 도움이 되었기를 바랍니다. 앞으로도 1인 개발자에게 유용한 콘텐츠로 찾아뵙겠습니다. 감사합니다!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[배포 자동화 — Vercel + GitHub 원클릭 배포]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 지난 포스팅에서 Next.js와 Supabase로 풀스택 서비스를 개발했습니다. 이제 만든 서비스를 세상에 내놓을 차례입니다. 하지만 배포라는 단어만 들어도 머리가 아프신 분들이 많을 겁니다. 서버 세팅, SSL…]]></description><link>https://blog.javapark.kr/04-deploy-automation/</link><guid isPermaLink="false">https://blog.javapark.kr/04-deploy-automation/</guid><pubDate>Sat, 04 Apr 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;지난 포스팅에서 Next.js와 Supabase로 풀스택 서비스를 개발했습니다. 이제 만든 서비스를 세상에 내놓을 차례입니다. 하지만 배포라는 단어만 들어도 머리가 아프신 분들이 많을 겁니다. 서버 세팅, SSL 인증서, CI/CD 파이프라인 구성... 과거에는 이 모든 것을 직접 해야 했습니다.&lt;/p&gt;
&lt;p&gt;2026년 현재, &lt;strong&gt;Vercel을 사용하면 &lt;code class=&quot;language-text&quot;&gt;git push&lt;/code&gt; 한 번으로 배포가 끝납니다.&lt;/strong&gt; 이번 포스팅에서는 Vercel과 GitHub를 연동해서 완전 자동 배포 환경을 구축하는 방법을 단계별로 알려드리겠습니다.&lt;/p&gt;
&lt;h2&gt;왜 Vercel인가?&lt;/h2&gt;
&lt;p&gt;1인 개발자에게 Vercel이 최적인 이유는 명확합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js 최적화&lt;/strong&gt;: Vercel은 Next.js를 만든 회사입니다. App Router, Server Components, Edge Runtime 등 Next.js의 모든 기능을 가장 먼저, 가장 잘 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무료 티어가 넉넉합니다&lt;/strong&gt;: Hobby 플랜으로 월 100GB 대역폭, 무제한 배포, 커스텀 도메인까지 무료로 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 배포&lt;/strong&gt;: GitHub에 push하면 자동으로 빌드하고 배포합니다. CI/CD 파이프라인을 따로 구성할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프리뷰 배포&lt;/strong&gt;: PR을 올리면 자동으로 프리뷰 URL이 생성됩니다. 코드 리뷰 없이도 변경 사항을 눈으로 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;글로벌 Edge Network&lt;/strong&gt;: 전 세계 어디서든 빠르게 접속할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vercel vs Netlify vs GitHub Pages 비교&lt;/h2&gt;
&lt;p&gt;배포 플랫폼을 고민하시는 분들을 위해 비교표를 준비했습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Vercel&lt;/th&gt;
&lt;th&gt;Netlify&lt;/th&gt;
&lt;th&gt;GitHub Pages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 지원&lt;/td&gt;
&lt;td&gt;완벽 (공식)&lt;/td&gt;
&lt;td&gt;좋음 (플러그인)&lt;/td&gt;
&lt;td&gt;정적만 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버리스 함수&lt;/td&gt;
&lt;td&gt;지원 (Edge/Node)&lt;/td&gt;
&lt;td&gt;지원 (Node)&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;무료 대역폭&lt;/td&gt;
&lt;td&gt;100GB/월&lt;/td&gt;
&lt;td&gt;100GB/월&lt;/td&gt;
&lt;td&gt;100GB/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드 시간&lt;/td&gt;
&lt;td&gt;6,000분/월&lt;/td&gt;
&lt;td&gt;300분/월&lt;/td&gt;
&lt;td&gt;2,000분/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프리뷰 배포&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커스텀 도메인&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge Functions&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분석 도구&lt;/td&gt;
&lt;td&gt;내장&lt;/td&gt;
&lt;td&gt;유료 애드온&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: Next.js 프로젝트라면 Vercel이 압도적으로 유리합니다. Netlify도 훌륭하지만, Next.js의 최신 기능을 100% 활용하려면 Vercel이 최선입니다. GitHub Pages는 정적 사이트 전용이므로 풀스택 프로젝트에는 적합하지 않습니다.&lt;/p&gt;
&lt;h2&gt;Vercel 계정 생성 및 GitHub 연동&lt;/h2&gt;
&lt;h3&gt;1단계: Vercel 가입&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com&quot;&gt;vercel.com&lt;/a&gt;에 접속해서 &lt;strong&gt;Sign Up&lt;/strong&gt; 버튼을 클릭합니다. GitHub 계정으로 가입하는 것을 추천합니다. 연동이 자동으로 이루어지기 때문입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. vercel.com 접속
2. &quot;Sign Up&quot; 클릭
3. &quot;Continue with GitHub&quot; 선택
4. GitHub 권한 허용
5. Hobby (무료) 플랜 선택&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: GitHub 저장소 권한 설정&lt;/h3&gt;
&lt;p&gt;가입 후 Vercel이 GitHub 저장소에 접근할 수 있도록 권한을 설정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;GitHub Settings → Applications → Vercel
→ &quot;Repository access&quot; 에서 배포할 저장소 선택
→ 전체 저장소를 허용하거나, 특정 저장소만 선택 가능&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;보안을 위해 &lt;strong&gt;특정 저장소만 선택&lt;/strong&gt;하는 것을 추천합니다. 필요할 때 저장소를 추가하면 됩니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 Import -- 첫 배포&lt;/h2&gt;
&lt;h3&gt;3단계: 프로젝트 가져오기&lt;/h3&gt;
&lt;p&gt;Vercel 대시보드에서 &quot;&lt;strong&gt;Add New Project&lt;/strong&gt;&quot; 를 클릭하고 GitHub 저장소를 선택합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Vercel 대시보드 → &quot;Add New Project&quot;
2. &quot;Import Git Repository&quot; 에서 저장소 선택
3. Framework Preset: Next.js (자동 감지됨)
4. Root Directory: ./ (기본값)
5. &quot;Deploy&quot; 클릭&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Vercel은 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;을 분석해서 프레임워크를 자동으로 감지합니다. Next.js 프로젝트라면 별도 설정 없이 바로 배포가 시작됩니다.&lt;/p&gt;
&lt;h3&gt;4단계: 빌드 설정 확인&lt;/h3&gt;
&lt;p&gt;대부분의 경우 기본 설정으로 충분하지만, 필요하다면 빌드 설정을 커스터마이징할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// vercel.json (프로젝트 루트에 생성)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;buildCommand&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next build&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;outputDirectory&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.next&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;installCommand&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm install&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;framework&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nextjs&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫 배포가 완료되면 &lt;code class=&quot;language-text&quot;&gt;https://프로젝트명.vercel.app&lt;/code&gt; 형태의 URL이 자동으로 생성됩니다. 이 URL로 바로 접속해서 서비스를 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;환경변수 설정&lt;/h2&gt;
&lt;p&gt;서비스가 정상 작동하려면 Supabase URL, API Key 등의 환경변수가 필요합니다. 로컬에서 &lt;code class=&quot;language-text&quot;&gt;.env.local&lt;/code&gt; 파일에 넣었던 값들을 Vercel에도 설정해야 합니다.&lt;/p&gt;
&lt;h3&gt;Vercel 대시보드에서 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Vercel 대시보드 → 프로젝트 선택
2. Settings → Environment Variables
3. 환경변수 추가&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;아래 환경변수들을 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Supabase 연동&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https://xxxx.supabase.co
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;eyJhbGciOiJIUzI1NiIs&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.

&lt;span class=&quot;token comment&quot;&gt;# OpenAI API (서버 사이드 전용)&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;sk-&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.

&lt;span class=&quot;token comment&quot;&gt;# 기타 설정&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_APP_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https://yourdomain.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;환경변수 스코프 설정&lt;/h3&gt;
&lt;p&gt;Vercel은 환경변수를 환경별로 분리할 수 있습니다.&lt;/p&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;Production&lt;/td&gt;
&lt;td&gt;실제 서비스&lt;/td&gt;
&lt;td&gt;실제 Supabase 프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;td&gt;PR 프리뷰&lt;/td&gt;
&lt;td&gt;테스트용 Supabase 프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;로컬 개발&lt;/td&gt;
&lt;td&gt;로컬 환경변수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Environment Variables 설정 화면에서:
→ 각 변수마다 Production / Preview / Development 체크박스로 스코프 지정
→ OPENAI_API_KEY 같은 민감한 키는 &quot;Sensitive&quot; 옵션 활성화&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;주의사항&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;NEXT_PUBLIC_&lt;/code&gt; 접두사가 붙은 변수는 클라이언트에 노출됩니다. API 시크릿 키에는 절대 &lt;code class=&quot;language-text&quot;&gt;NEXT_PUBLIC_&lt;/code&gt;을 붙이지 마세요.&lt;/p&gt;
&lt;h3&gt;Vercel CLI로 환경변수 관리&lt;/h3&gt;
&lt;p&gt;터미널에서도 환경변수를 관리할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Vercel CLI 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; vercel

&lt;span class=&quot;token comment&quot;&gt;# 로그인&lt;/span&gt;
vercel login

&lt;span class=&quot;token comment&quot;&gt;# 환경변수 추가&lt;/span&gt;
vercel &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; OPENAI_API_KEY production
&lt;span class=&quot;token comment&quot;&gt;# 프롬프트에서 값 입력&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 환경변수 목록 확인&lt;/span&gt;
vercel &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 환경변수를 로컬 .env.local로 가져오기&lt;/span&gt;
vercel &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt; pull .env.local&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;vercel env pull&lt;/code&gt; 명령은 팀원과 환경변수를 공유할 때 매우 유용합니다. &lt;code class=&quot;language-text&quot;&gt;.env.local&lt;/code&gt; 파일을 직접 주고받을 필요 없이, Vercel에서 바로 가져올 수 있습니다.&lt;/p&gt;
&lt;h2&gt;커스텀 도메인 연결&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;프로젝트명.vercel.app&lt;/code&gt; 도 좋지만, 자신만의 도메인으로 서비스하면 훨씬 전문적으로 보입니다.&lt;/p&gt;
&lt;h3&gt;도메인 구매&lt;/h3&gt;
&lt;p&gt;도메인은 다양한 곳에서 구매할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vercel Domains&lt;/strong&gt;: Vercel 대시보드에서 바로 구매 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Namecheap&lt;/strong&gt;: 저렴한 가격&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt;: DNS와 함께 관리 편리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가비아&lt;/strong&gt;: 한국 도메인(.kr) 등록 시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DNS 설정&lt;/h3&gt;
&lt;p&gt;도메인을 구매했다면 DNS 레코드를 설정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Vercel 대시보드 → 프로젝트 → Settings → Domains
2. 도메인 입력 (예: myapp.com)
3. &quot;Add&quot; 클릭
4. Vercel이 안내하는 DNS 레코드를 도메인 관리 사이트에 추가&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;일반적으로 아래 두 가지 레코드를 추가하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# A 레코드 (루트 도메인)
Type: A
Name: @
Value: 76.76.21.21

# CNAME 레코드 (www 서브도메인)
Type: CNAME
Name: www
Value: cname.vercel-dns.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;SSL 인증서 -- 자동 발급&lt;/h3&gt;
&lt;p&gt;Vercel은 도메인을 연결하면 &lt;strong&gt;Let&apos;s Encrypt SSL 인증서를 자동으로 발급&lt;/strong&gt;합니다. 별도의 설정이 필요 없습니다. 인증서 갱신도 자동으로 처리됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;도메인 연결 후 약 1~5분 내에:
→ SSL 인증서 자동 발급
→ HTTP → HTTPS 자동 리다이렉트 설정
→ www → non-www (또는 반대) 자동 리다이렉트&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;프리뷰 배포 -- PR마다 자동 미리보기 URL&lt;/h2&gt;
&lt;p&gt;프리뷰 배포는 Vercel의 가장 강력한 기능 중 하나입니다. Pull Request를 올리면 자동으로 해당 변경 사항이 적용된 별도의 URL이 생성됩니다.&lt;/p&gt;
&lt;h3&gt;작동 방식&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. 새 브랜치 생성: git checkout -b feature/new-ui
2. 코드 수정 후 push: git push origin feature/new-ui
3. GitHub에서 PR 생성
4. Vercel이 자동으로 프리뷰 빌드 시작
5. PR 코멘트에 프리뷰 URL 자동 게시
   → https://프로젝트-git-feature-new-ui-username.vercel.app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;프리뷰 배포 활용법&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 기능 브랜치 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; feature/dark-mode

&lt;span class=&quot;token comment&quot;&gt;# 코드 수정&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 커밋 및 푸시&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: 다크모드 추가&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin feature/dark-mode

&lt;span class=&quot;token comment&quot;&gt;# GitHub에서 PR 생성 후 Vercel 프리뷰 URL로 확인&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → 모바일에서도 접속해서 테스트 가능&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → 팀원이나 지인에게 URL 공유해서 피드백 받기&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1인 개발자라도 프리뷰 배포를 적극 활용하세요. 프로덕션에 바로 머지하기 전에 실제 환경에서 테스트할 수 있어서 실수를 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;h3&gt;프리뷰 배포 환경변수 분리&lt;/h3&gt;
&lt;p&gt;프리뷰 배포에서는 테스트용 환경변수를 사용하는 것이 안전합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Vercel 대시보드 → Settings → Environment Variables
→ NEXT_PUBLIC_SUPABASE_URL 변수를 Preview 환경에만 따로 설정
→ 테스트용 Supabase 프로젝트 URL 입력&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 프리뷰 배포에서 실수로 프로덕션 데이터를 건드리는 일을 방지할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;배포 최적화&lt;/h2&gt;
&lt;h3&gt;빌드 캐시 활용&lt;/h3&gt;
&lt;p&gt;Vercel은 빌드 캐시를 자동으로 관리합니다. 하지만 &lt;code class=&quot;language-text&quot;&gt;next.config.js&lt;/code&gt;에서 추가 최적화가 가능합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// next.config.js&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&apos;next&apos;).NextConfig} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 이미지 최적화&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/avif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;remotePatterns&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*.supabase.co&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 헤더 설정 (캐시 최적화)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/api/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;s-maxage=60, stale-while-revalidate=300&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/:path*.(jpg|jpeg|png|gif|ico|svg)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;public, max-age=31536000, immutable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nextConfig&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Edge Functions 활용&lt;/h3&gt;
&lt;p&gt;API 응답 속도를 높이고 싶다면 Edge Functions를 활용합니다. Edge Functions는 사용자와 가장 가까운 서버에서 실행되므로 지연 시간이 크게 줄어듭니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/api/hello/route.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; runtime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;edge&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Edge Runtime 사용&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; searchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;World&quot;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/api/og/route.tsx&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// OG 이미지 동적 생성 (Edge에서 실행)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ImageResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/og&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; runtime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;edge&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; searchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;기본 제목&quot;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div
        style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          display&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;flex&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          fontSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;linear-gradient(135deg, #667eea 0%, #764ba2 100%)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;100%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;100%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          alignItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          justifyContent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          padding&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;630&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;빌드 시간 단축 팁&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next build&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;vercel-build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next build&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// next.config.js에 추가&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 빌드 시 불필요한 페이지 제외 (개발용 페이지 등)&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;experimental&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 병렬 빌드 최적화&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;workerThreads&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;cpus&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;롤백 -- 문제 발생 시 이전 버전 복원&lt;/h2&gt;
&lt;p&gt;배포 후 문제가 발생했을 때 빠르게 이전 버전으로 되돌리는 것은 매우 중요합니다. Vercel은 이 과정을 매우 간단하게 만들어 줍니다.&lt;/p&gt;
&lt;h3&gt;대시보드에서 롤백&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. Vercel 대시보드 → 프로젝트 → Deployments 탭
2. 정상 작동하던 이전 배포를 찾기
3. 해당 배포의 &quot;...&quot; 메뉴 클릭
4. &quot;Promote to Production&quot; 선택
5. 즉시 이전 버전으로 복원 완료&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;CLI로 롤백&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 배포 목록 확인&lt;/span&gt;
vercel &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 특정 배포를 프로덕션으로 승격&lt;/span&gt;
vercel promote &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;deployment-url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 예시&lt;/span&gt;
vercel promote myapp-abc123.vercel.app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;롤백 전략&lt;/h3&gt;
&lt;p&gt;1인 개발자에게 추천하는 롤백 전략입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. 배포 전: 현재 프로덕션 URL 메모
2. 배포 후: 핵심 기능 5분 내 수동 테스트
3. 문제 발견: 즉시 이전 버전으로 롤백
4. 원인 분석: 로컬에서 디버깅 후 수정 배포&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;자동 배포 워크플로 전체 요약&lt;/h2&gt;
&lt;p&gt;최종적으로 완성된 배포 워크플로를 정리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[코드 수정] → [git push] → [Vercel 자동 빌드]
                                    ↓
                            [빌드 성공?]
                           /            \
                        Yes              No
                         ↓                ↓
                  [자동 배포]      [빌드 로그 확인]
                       ↓                  ↓
              [헬스 체크 통과?]    [코드 수정 후 재push]
                     ↓
            [프로덕션 반영 완료]&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;push&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git push origin main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;즉시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드&lt;/td&gt;
&lt;td&gt;Next.js 빌드 + 최적화&lt;/td&gt;
&lt;td&gt;1~3분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;Edge Network 배포&lt;/td&gt;
&lt;td&gt;30초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;인증서 확인&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;완료&lt;/td&gt;
&lt;td&gt;프로덕션 반영&lt;/td&gt;
&lt;td&gt;총 2~4분&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. Vercel 무료 플랜으로 실제 서비스 운영이 가능한가요?&lt;/h3&gt;
&lt;p&gt;네, 가능합니다. Vercel Hobby 플랜은 월 100GB 대역폭, 무제한 배포를 제공합니다. 일일 방문자가 수천 명 수준이라면 무료 플랜으로 충분합니다. 다만, 상업적 용도(팀 프로젝트, 유료 서비스)라면 Pro 플랜을 권장합니다. Hobby 플랜은 비상업적/개인 프로젝트 용도입니다.&lt;/p&gt;
&lt;h3&gt;Q2. 환경변수를 변경하면 자동으로 재배포되나요?&lt;/h3&gt;
&lt;p&gt;아닙니다. 환경변수를 변경한 후에는 &lt;strong&gt;수동으로 재배포&lt;/strong&gt;해야 합니다. Vercel 대시보드에서 Deployments 탭의 최신 배포에서 &quot;Redeploy&quot; 를 클릭하거나, 빈 커밋을 push하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 빈 커밋으로 재배포 트리거&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit --allow-empty &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chore: trigger redeploy&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Q3. 빌드가 실패했을 때 어떻게 디버깅하나요?&lt;/h3&gt;
&lt;p&gt;Vercel 대시보드에서 실패한 배포를 클릭하면 전체 빌드 로그를 확인할 수 있습니다. 가장 흔한 빌드 실패 원인은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;환경변수 미설정&lt;/strong&gt;: 로컬에서는 &lt;code class=&quot;language-text&quot;&gt;.env.local&lt;/code&gt;에 있지만 Vercel에 등록하지 않은 경우&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타입 에러&lt;/strong&gt;: 로컬에서는 무시했지만 &lt;code class=&quot;language-text&quot;&gt;next build&lt;/code&gt; 시 에러가 발생하는 경우&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존성 문제&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;package-lock.json&lt;/code&gt;이 커밋되지 않은 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;로컬에서 &lt;code class=&quot;language-text&quot;&gt;next build&lt;/code&gt;를 먼저 실행해서 빌드 에러를 확인하는 습관을 들이세요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;다음 포스팅에서는 서비스를 출시한 후 반드시 해야 할 &lt;strong&gt;운영과 모니터링&lt;/strong&gt; 에 대해 다룹니다. 에러 트래킹, 사용자 분석, 비용 관리까지 -- 1인 개발자가 서비스를 안정적으로 운영하는 방법을 알려드리겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AI로 풀스택 개발하기 — Claude Code + Supabase 실전]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "설계까지 끝났으니, 이제 진짜 코드를 작성할 차례입니다." 지난 두 포스팅에서 AI와 함께 기획하고, ERD/API…]]></description><link>https://blog.javapark.kr/03-fullstack-development/</link><guid isPermaLink="false">https://blog.javapark.kr/03-fullstack-development/</guid><pubDate>Sat, 04 Apr 2026 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;설계까지 끝났으니, 이제 진짜 코드를 작성할 차례입니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;지난 두 포스팅에서 AI와 함께 기획하고, ERD/API/와이어프레임까지 설계하는 과정을 다뤘습니다. 이번 포스팅에서는 드디어 &lt;strong&gt;실제 코드를 작성&lt;/strong&gt;합니다. Claude Code와 Supabase를 활용해서 &quot;독서 기록 웹앱&quot;의 핵심 기능을 구현하는 과정을 단계별로 보여드리겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;개발 환경 세팅 — Next.js + Supabase 프로젝트 초기화&lt;/h2&gt;
&lt;h3&gt;1단계: Next.js 프로젝트 생성&lt;/h3&gt;
&lt;p&gt;터미널에서 아래 명령어를 실행합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app@latest book-log &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--typescript&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--tailwind&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--eslint&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--app&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --src-dir &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  --import-alias &lt;span class=&quot;token string&quot;&gt;&quot;@/*&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: 필수 패키지 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; book-log

&lt;span class=&quot;token comment&quot;&gt;# Supabase&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @supabase/supabase-js @supabase/ssr

&lt;span class=&quot;token comment&quot;&gt;# UI 유틸리티&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; clsx tailwind-merge

&lt;span class=&quot;token comment&quot;&gt;# 아이콘&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; lucide-react

&lt;span class=&quot;token comment&quot;&gt;# 날짜 처리&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; date-fns&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3단계: 환경 변수 설정&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.env.local&lt;/code&gt; 파일을 생성하고, Supabase 프로젝트의 URL과 anon key를 입력합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https://your-project.supabase.co
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;eyJhbGciOiJIUzI1NiIsInR&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 값은 Supabase 대시보드 &gt; Settings &gt; API에서 확인할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Supabase 셋업 — 프로젝트 생성부터 테이블 생성까지&lt;/h2&gt;
&lt;h3&gt;Supabase 프로젝트 생성&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.com&quot;&gt;supabase.com&lt;/a&gt;에서 회원가입&lt;/li&gt;
&lt;li&gt;&quot;New Project&quot; 클릭&lt;/li&gt;
&lt;li&gt;프로젝트명: &lt;code class=&quot;language-text&quot;&gt;book-log&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;비밀번호 설정 (DB 접근용, 반드시 기록해두세요)&lt;/li&gt;
&lt;li&gt;리전: &lt;code class=&quot;language-text&quot;&gt;Northeast Asia (Tokyo)&lt;/code&gt; 선택&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;SQL Editor에서 테이블 생성&lt;/h3&gt;
&lt;p&gt;Supabase 대시보드 &gt; SQL Editor에서, 이전 포스팅에서 설계한 SQL을 실행합니다. 여기서 핵심 부분만 다시 정리하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- 커스텀 타입&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TYPE&lt;/span&gt; reading_status &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ENUM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;want_to_read&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;reading&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;finished&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TYPE&lt;/span&gt; memo_type &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ENUM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;note&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;quote&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;review&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- profiles 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; profiles &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  username &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  avatar_url &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- books 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; books &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  isbn &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  title &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  author &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  publisher &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  cover_image_url &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  page_count &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  published_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  description &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- reading_records 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; reading_records &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; books&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; reading_status &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;want_to_read&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  start_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  end_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  rating &lt;span class=&quot;token keyword&quot;&gt;SMALLINT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rating &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; rating &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; book_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- memos 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; memos &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; books&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  content &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  page_number &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  memo_type memo_type &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;note&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;신규 가입 시 자동 프로필 생성 트리거&lt;/h3&gt;
&lt;p&gt;사용자가 가입하면 자동으로 &lt;code class=&quot;language-text&quot;&gt;profiles&lt;/code&gt; 테이블에 레코드가 생성되도록 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_new_user&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $$
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profiles &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; avatar_url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    NEW&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    NEW&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_user_meta_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    NEW&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_user_meta_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;avatar_url&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; NEW&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$$ &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql SECURITY &lt;span class=&quot;token keyword&quot;&gt;DEFINER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; on_auth_user_created
  &lt;span class=&quot;token keyword&quot;&gt;AFTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users
  &lt;span class=&quot;token keyword&quot;&gt;FOR EACH ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_new_user&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;인증 구현 — Supabase Auth로 소셜 로그인&lt;/h2&gt;
&lt;h3&gt;Supabase 클라이언트 설정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/lib/supabase.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createBrowserClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@supabase/ssr&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBrowserClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/lib/supabase-server.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createServerClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@supabase/ssr&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookies &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/headers&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerSupabaseClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cookieStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      cookies&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cookieStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
              cookieStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Server Component에서는 쿠키 설정 불가&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;인증 미들웨어&lt;/h3&gt;
&lt;p&gt;인증되지 않은 사용자가 보호된 페이지에 접근하면 로그인 페이지로 리다이렉트합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createServerClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@supabase/ssr&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NextRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; supabaseResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      cookies&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
            request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          supabaseResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
            supabaseResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; user &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 인증되지 않은 사용자 리다이렉트&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;user &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/auth&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/login&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; supabaseResponse
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/((?!_next/static|_next/image|favicon.ico).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;로그인 페이지&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/app/login/page.tsx&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;use client&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LoginPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleGoogleLogin&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signInWithOAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      provider&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;google&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        redirectTo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/auth/callback&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleGithubLogin&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signInWithOAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      provider&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        redirectTo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/auth/callback&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex min-h-screen items-center justify-center bg-gray-50&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;w-full max-w-md space-y-8 rounded-xl bg-white p-8 shadow-lg&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-center&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h1 className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-3xl font-bold text-gray-900&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;BookLog&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h1&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mt-2 text-gray-600&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;나만의 독서 기록을 시작하세요&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;space-y-4&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
            onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleGoogleLogin&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex w-full items-center justify-center gap-3 rounded-lg border border-gray-300 bg-white px-4 py-3 text-gray-700 transition hover:bg-gray-50&quot;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;h-5 w-5&quot;&lt;/span&gt; viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 24 24&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;path
                fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#4285F4&quot;&lt;/span&gt;
                d&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z&quot;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;path
                fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#34A853&quot;&lt;/span&gt;
                d&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z&quot;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;path
                fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#FBBC05&quot;&lt;/span&gt;
                d&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z&quot;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;path
                fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#EA4335&quot;&lt;/span&gt;
                d&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z&quot;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            Google로 로그인
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button
            onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleGithubLogin&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex w-full items-center justify-center gap-3 rounded-lg bg-gray-900 px-4 py-3 text-white transition hover:bg-gray-800&quot;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;h-5 w-5&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;currentColor&quot;&lt;/span&gt; viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 24 24&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;path d&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            GitHub로 로그인
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;OAuth 콜백 처리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/app/auth/callback/route.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createServerSupabaseClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase-server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; searchParams&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerSupabaseClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exchangeCodeForSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;origin&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/dashboard&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;CRUD 구현 — Claude Code로 &quot;독서 기록 CRUD 만들어줘&quot;&lt;/h2&gt;
&lt;p&gt;이제 핵심 기능인 독서 기록 CRUD를 구현합니다. Claude Code에게 아래와 같이 요청하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;reading_records 테이블에 대한 CRUD 서비스 함수를 만들어줘.

- Supabase JS Client 사용
- TypeScript
- 에러 처리 포함
- book 정보도 함께 조회 (join)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;서비스 레이어 — 독서 기록 CRUD&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/services/records.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ReadingStatus &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/types/database&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 내 독서 기록 전체 조회&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMyRecords&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingStatus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
      *,
      book:books(*)
    &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ascending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; query
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 독서 기록 생성&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRecord&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  bookId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingStatus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;want_to_read&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; user &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;인증이 필요합니다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      user_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      book_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bookId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      start_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reading&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;*, book:books(*)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 독서 상태 변경&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateRecordStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  recordId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingStatus
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updateData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reading&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    updateData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start_date &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;finished&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    updateData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;end_date &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updateData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;*, book:books(*)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 별점 등록&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateRating&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rating&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; rating &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 독서 기록 삭제&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteRecord&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recordId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;책 검색 및 등록 서비스&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/services/books.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 알라딘 API로 ISBN 검색 (서버사이드)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchBookByISBN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isbn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/books/search?isbn=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;isbn&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;책을 찾을 수 없습니다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 제목으로 책 검색 (DB)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchBooks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;title.ilike.%&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;query&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%,author.ilike.%&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;query&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 책 등록 (DB에 없는 경우)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerBook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;book&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  isbn&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  author&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  publisher&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  cover_image_url&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  page_count&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;
  description&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ISBN으로 중복 체크&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; existing &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;isbn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; existing
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;ISBN 검색 API 라우트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/app/api/books/search/route.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ALADIN_API_KEY&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ALADIN_API_KEY&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; searchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isbn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;isbn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ISBN이 필요합니다&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 알라딘 API 호출&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;http://www.aladin.co.kr/ttb/api/ItemLookUp.aspx?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
        &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ttbkey=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ALADIN_API_KEY&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;itemIdType=ISBN13&amp;amp;ItemId=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;isbn&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
        &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;output=js&amp;amp;Version=20131101&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;item &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;책을 찾을 수 없습니다&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      isbn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; isbn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      author&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;author&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      publisher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;publisher&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      cover_image_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cover&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      page_count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subInfo&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;itemPage &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      published_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pubDate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;검색 중 오류가 발생했습니다&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;UI 구현 — Tailwind CSS로 반응형 대시보드&lt;/h2&gt;
&lt;h3&gt;대시보드 페이지&lt;/h3&gt;
&lt;p&gt;Claude Code에게 &quot;이전 포스팅의 와이어프레임 기반으로 대시보드를 구현해줘&quot;라고 요청하면 아래와 같은 코드를 생성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/app/dashboard/page.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createServerSupabaseClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase-server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; BookList &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/components/books/BookList&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ReadingStats &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/components/records/ReadingStats&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; redirect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/navigation&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DashboardPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerSupabaseClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; user &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 독서 기록 조회 (책 정보 포함)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; records &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;reading_records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;*, book:books(*)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ascending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reading &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; records&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reading&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; finished &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; records&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;finished&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wantToRead &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; records&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;want_to_read&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mx-auto max-w-6xl px-4 py-8&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 독서 통계 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ReadingStats
        readingCount&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;reading&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        finishedCount&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;finished&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        wantToReadCount&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;wantToRead&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 탭 기반 독서 목록 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mt-8&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BookList
          reading&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;reading&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          finished&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;finished&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          wantToRead&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;wantToRead&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;책 카드 컴포넌트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/books/BookCard.tsx&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;use client&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Image &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/image&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Link &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/link&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Star &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lucide-react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ReadingRecord &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/types/database&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BookCardProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  record&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingRecord &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; book&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BookCard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; record &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BookCardProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; book&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rating &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; record

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; statusLabel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    reading&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;읽는 중&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    finished&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;완료&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    want_to_read&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;읽고 싶은&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; statusColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    reading&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-blue-100 text-blue-800&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    finished&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-green-100 text-green-800&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    want_to_read&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-yellow-100 text-yellow-800&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Link href&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/books/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;group rounded-lg border border-gray-200 bg-white p-4 transition hover:shadow-md&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 책 커버 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;relative aspect-[3/4] w-full overflow-hidden rounded-md bg-gray-100&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cover_image_url &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Image
              src&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cover_image_url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              alt&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; 표지&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              fill
              className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;object-cover transition group-hover:scale-105&quot;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex h-full items-center justify-center text-gray-400&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              No Cover
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 책 정보 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mt-3 space-y-1&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;span
            className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;inline-block rounded-full px-2 py-0.5 text-xs font-medium &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;statusColor&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;statusLabel&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;span&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h3 className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;line-clamp-2 text-sm font-semibold text-gray-900&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h3&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-xs text-gray-500&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;author&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 별점 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;rating &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex gap-0.5&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Star
                  key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                  className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;h-3.5 w-3.5 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;
                    i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; rating
                      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fill-yellow-400 text-yellow-400&quot;&lt;/span&gt;
                      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text-gray-300&quot;&lt;/span&gt;
                  &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Link&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;독서 통계 컴포넌트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/records/ReadingStats.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; BookOpen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CheckCircle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Bookmark &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lucide-react&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadingStatsProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  readingCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;
  finishedCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;
  wantToReadCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ReadingStats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  readingCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  finishedCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  wantToReadCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingStatsProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stats &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;읽는 중&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; readingCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      icon&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BookOpen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text-blue-600 bg-blue-50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;완료&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; finishedCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      icon&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckCircle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text-green-600 bg-green-50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;읽고 싶은&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; wantToReadCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      icon&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bookmark&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text-yellow-600 bg-yellow-50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;grid grid-cols-3 gap-4&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;stats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stat &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div
          key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rounded-lg border border-gray-200 bg-white p-4&quot;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flex items-center gap-3&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;rounded-lg p-2 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;color&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;icon className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;h-5 w-5&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-2xl font-bold text-gray-900&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
              &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text-sm text-gray-500&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code 활용 팁 — 효과적인 프롬프트 작성법&lt;/h2&gt;
&lt;h3&gt;1. CLAUDE.md로 프로젝트 컨텍스트 공유하기&lt;/h3&gt;
&lt;p&gt;프로젝트 루트에 &lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 파일을 두면, Claude Code가 매번 새 대화를 시작할 때 자동으로 읽어서 컨텍스트를 파악합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; BookLog&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 기술 스택&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Next.js 14 (App Router, TypeScript)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Supabase (PostgreSQL, Auth, RLS)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Tailwind CSS
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Vercel 배포

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; DB 스키마&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; profiles: 사용자 프로필 (auth.users 연동)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; books: 책 정보 (ISBN, 제목, 저자)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; reading_records: 독서 기록 (상태, 별점, 날짜)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; memos: 독서 메모 (노트, 인용, 리뷰)

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 코딩 컨벤션&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 컴포넌트: src/components/ 하위 기능별 분류
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 서비스: src/services/ (Supabase 쿼리 로직)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 서버 컴포넌트 기본, 클라이언트 컴포넌트는 &apos;use client&apos; 명시
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 에러 처리: try/catch + 사용자 친화적 메시지&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. 구체적인 지시를 내리세요&lt;/h3&gt;
&lt;p&gt;나쁜 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;메모 기능 만들어줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;좋은 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;memos 테이블에 대한 CRUD를 구현해줘.

- src/services/memos.ts에 서비스 함수
- src/components/memos/MemoEditor.tsx에 작성/수정 폼
- src/components/memos/MemoCard.tsx에 메모 카드
- 메모 타입: note, quote, review (라디오 버튼으로 선택)
- 인용구(quote) 타입은 페이지 번호 입력 필드 추가
- Tailwind CSS, 반응형&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. 단계별로 요청하세요&lt;/h3&gt;
&lt;p&gt;한 번에 모든 걸 요청하면 결과물의 품질이 떨어집니다. &lt;strong&gt;서비스 함수 -&gt; 컴포넌트 -&gt; 페이지&lt;/strong&gt; 순서로 나눠서 요청하는 것이 좋습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1단계: &quot;memos 서비스 함수만 만들어줘&quot; -&gt; 검토 -&gt; 확정
2단계: &quot;이 서비스를 사용하는 MemoEditor 컴포넌트 만들어줘&quot; -&gt; 검토 -&gt; 확정
3단계: &quot;대시보드에 메모 섹션 추가해줘&quot; -&gt; 검토 -&gt; 확정&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;테스트 — AI가 만든 코드를 검증하는 방법&lt;/h2&gt;
&lt;p&gt;AI가 생성한 코드를 &lt;strong&gt;무조건 신뢰하면 안 됩니다.&lt;/strong&gt; 특히 아래 항목은 반드시 직접 확인하세요.&lt;/p&gt;
&lt;h3&gt;수동 테스트 체크리스트&lt;/h3&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;인증&lt;/td&gt;
&lt;td&gt;로그인/로그아웃 정상 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RLS&lt;/td&gt;
&lt;td&gt;다른 사용자의 데이터가 보이지 않는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRUD&lt;/td&gt;
&lt;td&gt;생성/조회/수정/삭제 각각 동작 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;엣지 케이스&lt;/td&gt;
&lt;td&gt;빈 입력, 긴 텍스트, 특수문자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;반응형&lt;/td&gt;
&lt;td&gt;모바일/태블릿/데스크톱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 처리&lt;/td&gt;
&lt;td&gt;네트워크 오류, 서버 오류 시 UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Claude Code로 테스트 코드 생성하기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;BookCard 컴포넌트의 단위 테스트를 작성해줘.

- Vitest + React Testing Library 사용
- 테스트 케이스:
  1. 책 제목과 저자가 렌더링되는지
  2. 상태에 따라 올바른 배지 색상이 적용되는지
  3. 별점이 올바르게 표시되는지
  4. 커버 이미지가 없을 때 대체 텍스트가 표시되는지&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;생성된 테스트 코드 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/books/BookCard.test.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@testing-library/react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; describe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vitest&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; BookCard &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./BookCard&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mockRecord &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;book-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reading&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  rating&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  start_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  end_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;book-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    isbn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;9788936434267&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;채식주의자&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    author&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;한강&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    publisher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;창비&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    cover_image_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://example.com/cover.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    page_count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;247&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    published_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2007-10-30&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;BookCard&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;책 제목과 저자가 렌더링된다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BookCard record&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;mockRecord&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;채식주의자&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;한강&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;읽는 중 상태 배지가 표시된다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BookCard record&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;mockRecord&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; badge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;읽는 중&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;badge&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bg-blue-100&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;별점 4개가 채워져 표시된다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BookCard record&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;mockRecord&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filledStars &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.fill-yellow-400&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filledStars&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;커버 이미지가 없으면 대체 텍스트가 표시된다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; noCoverRecord &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;mockRecord&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      book&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;mockRecord&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;book&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cover_image_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BookCard record&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;noCoverRecord&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No Cover&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;RLS 테스트 — 가장 중요한 보안 검증&lt;/h3&gt;
&lt;p&gt;RLS가 제대로 동작하는지 확인하는 가장 간단한 방법은 &lt;strong&gt;Supabase SQL Editor에서 직접 쿼리를 실행&lt;/strong&gt;하는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- 특정 사용자로 로그인한 상태를 시뮬레이션&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jwt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;claims &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;sub&quot;: &quot;user-id-here&quot;}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 이 쿼리로 해당 사용자의 데이터만 조회되는지 확인&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; reading_records&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;개발 결과물 요약&lt;/h2&gt;
&lt;p&gt;이 세 포스팅에 걸쳐 만든 &quot;독서 기록 웹앱&quot;의 구성을 정리하면 다음과 같습니다.&lt;/p&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;프론트엔드&lt;/td&gt;
&lt;td&gt;Next.js 14 (App Router) + Tailwind CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;백엔드&lt;/td&gt;
&lt;td&gt;Supabase (PostgreSQL + Auth + RLS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증&lt;/td&gt;
&lt;td&gt;Google, GitHub 소셜 로그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 기능&lt;/td&gt;
&lt;td&gt;책 검색/등록, 독서 기록 CRUD, 메모 CRUD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 도구&lt;/td&gt;
&lt;td&gt;Claude Code (설계, 코드 생성, 테스트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개발 기간&lt;/td&gt;
&lt;td&gt;약 2주 (기획 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. Claude Code 없이 다른 AI 코딩 도구로도 이 과정이 가능한가요?&lt;/h3&gt;
&lt;p&gt;가능합니다. GitHub Copilot, Cursor 등도 비슷한 작업을 할 수 있습니다. 다만 Claude Code는 &lt;strong&gt;프로젝트 전체 컨텍스트를 이해&lt;/strong&gt;하고 파일 간의 관계를 파악하는 데 강점이 있어서, 이 시리즈에서는 Claude Code를 중심으로 설명했습니다.&lt;/p&gt;
&lt;h3&gt;Q2. Supabase 무료 플랜으로 실제 서비스 운영이 가능한가요?&lt;/h3&gt;
&lt;p&gt;사이드 프로젝트 수준이라면 충분합니다. 무료 플랜은 500MB 데이터베이스, 1GB 파일 스토리지, 50,000 월간 활성 사용자를 제공합니다. 다만 프로젝트가 7일간 비활성 상태면 일시 중지되므로, 실제 서비스 운영 시에는 Pro 플랜(월 $25)을 고려하세요.&lt;/p&gt;
&lt;h3&gt;Q3. AI가 생성한 코드의 품질은 믿을 만한가요?&lt;/h3&gt;
&lt;p&gt;&quot;초안&quot;으로는 훌륭하지만, &lt;strong&gt;프로덕션에 바로 쓰기에는 부족한 경우가 많습니다.&lt;/strong&gt; 특히 에러 처리, 엣지 케이스, 보안(RLS 정책) 부분은 반드시 직접 검토하세요. AI를 &quot;주니어 개발자&quot;라고 생각하면 됩니다 — 빠르게 코드를 짜지만, 시니어의 리뷰가 필요한 수준입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분도 AI와 함께 사이드 프로젝트를 만들어보고 계신가요? 어떤 도구를 쓰고, 어떤 점이 좋았는지 댓글로 공유해주세요!&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 &quot;&lt;strong&gt;Vercel + GitHub Actions로 자동 배포 파이프라인 구축하기&lt;/strong&gt;&quot;를 주제로, 만든 앱을 세상에 공개하는 과정을 다뤄보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[설계는 AI에게 맡겨라 — ERD, API, 와이어프레임 자동화]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "코드부터 짜고 싶은 충동, 이해합니다. 하지만 설계 없이 시작한 프로젝트의 결말은 대부분 같습니다." 지난 포스팅에서 AI와 함께 아이디어를 검증하고 PRD…]]></description><link>https://blog.javapark.kr/02-ai-design/</link><guid isPermaLink="false">https://blog.javapark.kr/02-ai-design/</guid><pubDate>Sat, 04 Apr 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;코드부터 짜고 싶은 충동, 이해합니다. 하지만 설계 없이 시작한 프로젝트의 결말은 대부분 같습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;지난 포스팅에서 AI와 함께 아이디어를 검증하고 PRD를 작성하는 과정을 다뤘습니다. 이번에는 그다음 단계인 &lt;strong&gt;설계&lt;/strong&gt;를 AI에게 맡기는 방법을 공유합니다. ERD, API 명세, 와이어프레임, 프로젝트 구조까지 — Claude Code 하나로 해결하는 과정을 보여드리겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설계를 건너뛰면 생기는 일&lt;/h2&gt;
&lt;p&gt;1인 개발자가 가장 흔히 하는 실수 중 하나는 &quot;&lt;strong&gt;일단 코드부터 짜자&lt;/strong&gt;&quot;입니다. 저도 수없이 겪었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테이블 하나 추가하려니 기존 구조와 충돌&lt;/li&gt;
&lt;li&gt;API 엔드포인트 네이밍이 중구난방&lt;/li&gt;
&lt;li&gt;화면을 만들다 보니 필요한 데이터가 빠져 있어서 DB 수정&lt;/li&gt;
&lt;li&gt;결국 전체 리팩토링 — 사실상 처음부터 다시 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;설계에 &lt;strong&gt;2-3시간만 투자&lt;/strong&gt;하면, 개발 과정에서 수십 시간을 아낄 수 있습니다. 그리고 이제는 그 2-3시간마저 AI가 크게 줄여줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;ERD 설계 — Claude Code에게 데이터베이스 설계 맡기기&lt;/h2&gt;
&lt;h3&gt;프롬프트: Supabase 테이블 설계 요청&lt;/h3&gt;
&lt;p&gt;Claude Code에 아래와 같이 요청합니다. 핵심은 &lt;strong&gt;지난 포스팅에서 만든 PRD를 컨텍스트로 함께 전달&lt;/strong&gt;하는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;이 PRD를 기반으로 Supabase(PostgreSQL) 데이터베이스를 설계해줘.

요구사항:
- users, books, reading_records, memos 테이블
- Supabase Auth의 auth.users와 연동
- RLS(Row Level Security) 정책 포함
- created_at, updated_at 자동 관리
- 적절한 인덱스 포함

결과물:
1. ERD (텍스트 기반)
2. Supabase SQL (테이블 생성 + RLS + 인덱스)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Claude Code가 생성한 ERD&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────────────┐     ┌──────────────────────┐
│     profiles     │     │        books          │
├─────────────────┤     ├──────────────────────┤
│ id (uuid, PK)   │──┐  │ id (uuid, PK)         │
│ username         │  │  │ isbn (varchar, UQ)     │
│ avatar_url       │  │  │ title                  │
│ created_at       │  │  │ author                 │
│ updated_at       │  │  │ publisher              │
└─────────────────┘  │  │ cover_image_url        │
                      │  │ page_count             │
                      │  │ published_date         │
                      │  │ description            │
                      │  │ created_at             │
                      │  └──────────────────────┘
                      │
                      │  ┌──────────────────────┐
                      │  │   reading_records      │
                      │  ├──────────────────────┤
                      ├──│ user_id (uuid, FK)     │
                      │  │ book_id (uuid, FK)     │
                      │  │ status (enum)          │
                      │  │ start_date             │
                      │  │ end_date               │
                      │  │ rating (1-5)           │
                      │  │ created_at             │
                      │  │ updated_at             │
                      │  └──────────────────────┘
                      │
                      │  ┌──────────────────────┐
                      │  │       memos            │
                      │  ├──────────────────────┤
                      └──│ user_id (uuid, FK)     │
                         │ book_id (uuid, FK)     │
                         │ content (text)         │
                         │ page_number (int)      │
                         │ memo_type (enum)       │
                         │ created_at             │
                         │ updated_at             │
                         └──────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Supabase SQL — 테이블 생성&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- 1. 커스텀 타입 정의&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TYPE&lt;/span&gt; reading_status &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ENUM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;want_to_read&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;reading&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;finished&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TYPE&lt;/span&gt; memo_type &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ENUM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;note&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;quote&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;review&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 2. profiles 테이블 (Supabase Auth 연동)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; profiles &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  username &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  avatar_url &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 3. books 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; books &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  isbn &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  title &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  author &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  publisher &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  cover_image_url &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  page_count &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  published_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  description &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 4. reading_records 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; reading_records &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; books&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; reading_status &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;want_to_read&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  start_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  end_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  rating &lt;span class=&quot;token keyword&quot;&gt;SMALLINT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rating &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; rating &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; book_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 5. memos 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; memos &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id UUID &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  book_id UUID &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; books&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  content &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  page_number &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  memo_type memo_type &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;note&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  updated_at TIMESTAMPTZ &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 6. 인덱스&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_reading_records_user &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; reading_records&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_reading_records_status &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; reading_records&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_memos_user_book &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; memos&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; book_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_books_isbn &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; books&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 7. updated_at 자동 갱신 트리거&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; update_updated_at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $$
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
  NEW&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updated_at &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; NEW&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$$ &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; profiles_updated_at
  BEFORE &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; profiles
  &lt;span class=&quot;token keyword&quot;&gt;FOR EACH ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; update_updated_at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; reading_records_updated_at
  BEFORE &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; reading_records
  &lt;span class=&quot;token keyword&quot;&gt;FOR EACH ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; update_updated_at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; memos_updated_at
  BEFORE &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; memos
  &lt;span class=&quot;token keyword&quot;&gt;FOR EACH ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; update_updated_at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;RLS 정책 설정 — 보안의 핵심&lt;/h3&gt;
&lt;p&gt;Supabase에서 RLS는 선택이 아니라 필수입니다. 각 사용자가 &lt;strong&gt;자신의 데이터만 읽고 쓸 수 있도록&lt;/strong&gt; 정책을 설정합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- RLS 활성화&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; profiles &lt;span class=&quot;token keyword&quot;&gt;ENABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; SECURITY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; reading_records &lt;span class=&quot;token keyword&quot;&gt;ENABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; SECURITY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; memos &lt;span class=&quot;token keyword&quot;&gt;ENABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; SECURITY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- profiles: 본인만 조회/수정&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Users can view own profile&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; profiles &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Users can update own profile&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; profiles &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- reading_records: 본인만 CRUD&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Users can manage own records&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; reading_records &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- memos: 본인만 CRUD&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Users can manage own memos&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; memos &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- books: 누구나 조회 가능, 인증된 사용자만 추가 가능&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; books &lt;span class=&quot;token keyword&quot;&gt;ENABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; SECURITY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Anyone can view books&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; books &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; POLICY &lt;span class=&quot;token string&quot;&gt;&quot;Authenticated users can insert books&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; books &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;authenticated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;API 명세 자동 생성 — RESTful 엔드포인트 설계&lt;/h2&gt;
&lt;p&gt;Supabase는 테이블 기반으로 자동 REST API를 제공하지만, 프론트엔드에서 사용할 &lt;strong&gt;서비스 레이어의 함수 명세&lt;/strong&gt;를 미리 정의해두면 개발이 훨씬 매끄럽습니다.&lt;/p&gt;
&lt;h3&gt;API 명세 생성 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;위 DB 스키마 기반으로 프론트엔드에서 사용할 API 함수 명세를 만들어줘.

조건:
- Supabase JS Client 사용
- TypeScript 타입 정의 포함
- 에러 처리 패턴 포함

카테고리:
1. Auth (로그인, 로그아웃, 프로필)
2. Books (검색, 등록)
3. Reading Records (CRUD, 상태 변경)
4. Memos (CRUD)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;생성된 TypeScript 타입 정의&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// types/database.ts&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadingStatus&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;want_to_read&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reading&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;finished&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemoType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;note&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;quote&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;review&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Profile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  username&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  avatar_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  updated_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Book&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  isbn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  author&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  publisher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  cover_image_url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  page_count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  published_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadingRecord&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  user_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  book_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadingStatus
  start_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  end_date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  rating&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  updated_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  book&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Book &lt;span class=&quot;token comment&quot;&gt;// join 시&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Memo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  user_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  book_id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  page_number&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
  memo_type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MemoType
  created_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  updated_at&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;서비스 함수 명세 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// services/books.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; supabase &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/lib/supabase&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Book &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/types/database&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ISBN으로 책 검색 (외부 API)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchBookByISBN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isbn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Book &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 1. 먼저 DB에서 검색&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;isbn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data

  &lt;span class=&quot;token comment&quot;&gt;// 2. DB에 없으면 알라딘 API로 검색 후 DB에 저장&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bookInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchFromAladinAPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;bookInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; newBook&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bookInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; newBook
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 제목으로 책 검색&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchBooksByTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Book&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ilike&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;query&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;와이어프레임 — AI로 화면 구조 잡기&lt;/h2&gt;
&lt;p&gt;디자이너 없이 화면을 설계해야 하는 1인 개발자에게, &lt;strong&gt;텍스트 기반 와이어프레임&lt;/strong&gt;은 매우 실용적인 방법입니다.&lt;/p&gt;
&lt;h3&gt;와이어프레임 생성 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;독서 기록 웹앱의 주요 화면 와이어프레임을 텍스트로 만들어줘.

필요한 화면:
1. 대시보드 (내 독서 목록)
2. 책 상세 페이지
3. 책 등록 페이지 (ISBN 검색)
4. 메모 작성 페이지

각 화면에 포함할 것:
- 레이아웃 구조 (ASCII 아트)
- 필요한 컴포넌트 목록
- 사용자 인터랙션 흐름&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;대시보드 와이어프레임&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────────────────────────────────────────────┐
│  [Logo] BookLog        [검색]    [프로필 아바타] │
├─────────────────────────────────────────────────┤
│                                                  │
│  ┌─ 탭 ──────────────────────────────────────┐  │
│  │ [읽는 중 (3)]  [완료 (12)]  [읽고 싶은 (5)]│  │
│  └───────────────────────────────────────────┘  │
│                                                  │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐         │
│  │ [커버]  │  │ [커버]  │  │ [커버]  │         │
│  │ 제목    │  │ 제목    │  │ 제목    │         │
│  │ 저자    │  │ 저자    │  │ 저자    │         │
│  │ ★★★★☆ │  │ ★★★☆☆ │  │         │         │
│  │ 메모 3  │  │ 메모 1  │  │ [등록]  │         │
│  └─────────┘  └─────────┘  └─────────┘         │
│                                                  │
│              [+ 새 책 등록]                      │
│                                                  │
├─────────────────────────────────────────────────┤
│  올해 읽은 책: 12권 / 목표 24권  [████░░] 50%   │
└─────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 와이어프레임을 기반으로 실제 UI를 구현할 때, Claude Code에게 &quot;&lt;strong&gt;이 와이어프레임대로 Tailwind CSS로 구현해줘&lt;/strong&gt;&quot;라고 요청하면 꽤 정확한 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기술 스택 선정 — Next.js + Supabase + Vercel 조합의 이유&lt;/h2&gt;
&lt;p&gt;1인 개발자에게 기술 스택 선택은 &lt;strong&gt;생산성&lt;/strong&gt;이 최우선입니다. 화려한 스택보다 &lt;strong&gt;빠르게 만들고, 쉽게 배포하고, 저렴하게 운영&lt;/strong&gt;할 수 있는 조합이 중요합니다.&lt;/p&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;프론트엔드&lt;/td&gt;
&lt;td&gt;Next.js (App Router)&lt;/td&gt;
&lt;td&gt;React 생태계, SSR/SSG 지원, Vercel 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;백엔드/DB&lt;/td&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;td&gt;PostgreSQL + Auth + Storage + Realtime 올인원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스타일링&lt;/td&gt;
&lt;td&gt;Tailwind CSS&lt;/td&gt;
&lt;td&gt;유틸리티 기반, AI가 생성하기 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Git push로 자동 배포, 무료 플랜 충분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 개발 도구&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;코드 생성, 리팩토링, 디버깅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;왜 Supabase인가?&lt;/h3&gt;
&lt;p&gt;솔직히 말하면, Firebase도 좋은 선택입니다. 하지만 Supabase를 선택한 이유는 세 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; — 관계형 DB의 강력함 (JOIN, 트랜잭션)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RLS&lt;/strong&gt; — 별도 백엔드 서버 없이 보안 처리 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무료 플랜&lt;/strong&gt; — 사이드 프로젝트에 충분한 무료 티어 (500MB DB, 1GB Storage)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;프로젝트 구조 생성 — Claude Code로 보일러플레이트 자동 생성&lt;/h2&gt;
&lt;h3&gt;프로젝트 구조 생성 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Next.js 14 (App Router) + Supabase + Tailwind CSS 프로젝트 구조를 만들어줘.

구조 규칙:
- app/ 디렉토리 사용 (App Router)
- 컴포넌트는 components/ 하위에 기능별로 분류
- Supabase 클라이언트는 lib/supabase.ts
- 타입 정의는 types/
- 서비스 로직은 services/

기능:
- 인증 (로그인/회원가입)
- 책 관리 (검색, 등록)
- 독서 기록 (CRUD)
- 메모 (CRUD)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Claude Code가 생성한 프로젝트 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;book-log/
├── app/
│   ├── layout.tsx              # 루트 레이아웃
│   ├── page.tsx                # 랜딩 페이지
│   ├── globals.css
│   ├── (auth)/
│   │   ├── login/page.tsx      # 로그인
│   │   └── callback/route.ts   # OAuth 콜백
│   ├── (main)/
│   │   ├── layout.tsx          # 인증된 사용자 레이아웃
│   │   ├── dashboard/page.tsx  # 대시보드
│   │   ├── books/
│   │   │   ├── page.tsx        # 책 검색/등록
│   │   │   └── [id]/page.tsx   # 책 상세
│   │   └── memos/
│   │       └── [bookId]/page.tsx # 메모 목록
│   └── api/
│       └── books/
│           └── search/route.ts  # ISBN 검색 API
├── components/
│   ├── ui/                     # 공통 UI 컴포넌트
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   ├── Card.tsx
│   │   └── Modal.tsx
│   ├── auth/
│   │   └── AuthButton.tsx
│   ├── books/
│   │   ├── BookCard.tsx
│   │   ├── BookSearch.tsx
│   │   └── BookList.tsx
│   ├── records/
│   │   ├── StatusBadge.tsx
│   │   └── RecordForm.tsx
│   └── memos/
│       ├── MemoCard.tsx
│       └── MemoEditor.tsx
├── lib/
│   ├── supabase.ts             # Supabase 클라이언트 (브라우저)
│   └── supabase-server.ts      # Supabase 클라이언트 (서버)
├── services/
│   ├── auth.ts
│   ├── books.ts
│   ├── records.ts
│   └── memos.ts
├── types/
│   └── database.ts             # DB 타입 정의
├── middleware.ts                # 인증 미들웨어
└── .env.local                  # 환경 변수&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Supabase 클라이언트 설정 코드&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// lib/supabase.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createBrowserClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@supabase/ssr&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBrowserClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// lib/supabase-server.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createServerClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@supabase/ssr&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookies &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/headers&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cookieStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServerClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      cookies&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cookieStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cookiesToSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
            cookieStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;설계 단계에서 Claude Code를 잘 쓰는 팁&lt;/h2&gt;
&lt;h3&gt;CLAUDE.md 파일 활용하기&lt;/h3&gt;
&lt;p&gt;프로젝트 루트에 &lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 파일을 만들어서 프로젝트 컨텍스트를 미리 정의해두면, Claude Code가 훨씬 일관된 결과를 생성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; BookLog 프로젝트&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 기술 스택&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Next.js 14 (App Router)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Supabase (PostgreSQL, Auth, Storage)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Tailwind CSS
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; TypeScript

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 코딩 컨벤션&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 컴포넌트: PascalCase (BookCard.tsx)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 함수/변수: camelCase
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 타입: PascalCase (interface Book)
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 파일 구조: 기능별 분류

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; DB 스키마&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; profiles, books, reading_records, memos
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; RLS 사용, auth.uid() 기반 권한 관리&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. Supabase를 처음 쓰는데, RLS 설정이 어렵지 않나요?&lt;/h3&gt;
&lt;p&gt;솔직히 처음에는 헷갈립니다. 하지만 패턴이 정해져 있어서 한 번 이해하면 쉽습니다. 핵심은 &lt;code class=&quot;language-text&quot;&gt;auth.uid() = user_id&lt;/code&gt; 하나입니다. &quot;이 행의 user_id가 로그인한 사용자의 id와 같으면 허용&quot;이라는 뜻이죠. 이 포스팅의 SQL을 그대로 사용하셔도 됩니다.&lt;/p&gt;
&lt;h3&gt;Q2. ERD 설계를 AI에게 맡기면 문제가 생기지 않나요?&lt;/h3&gt;
&lt;p&gt;AI가 생성한 스키마를 &lt;strong&gt;그대로 프로덕션에 쓰면&lt;/strong&gt; 문제가 될 수 있습니다. 하지만 초기 설계의 출발점으로는 훌륭합니다. 중요한 건 생성된 스키마를 &lt;strong&gt;직접 검토&lt;/strong&gt;하는 것입니다. 특히 인덱스, 제약 조건, 데이터 타입은 반드시 확인하세요.&lt;/p&gt;
&lt;h3&gt;Q3. 와이어프레임을 텍스트로 만드는 게 Figma보다 나은가요?&lt;/h3&gt;
&lt;p&gt;&quot;나은가&quot;보다는 &quot;빠른가&quot;의 문제입니다. Figma로 완성도 높은 디자인을 만들 수 있지만, 1인 개발자에게 그 시간은 사치일 수 있습니다. 텍스트 와이어프레임으로 &lt;strong&gt;구조를 먼저 잡고&lt;/strong&gt;, 필요하면 나중에 Figma로 다듬는 순서를 추천합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 사이드 프로젝트 설계할 때 어떤 도구를 쓰고 계신가요? 댓글로 공유해주세요!&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 &quot;&lt;strong&gt;AI로 풀스택 개발하기 — Claude Code + Supabase 실전&lt;/strong&gt;&quot;을 주제로, 실제 코드를 작성하며 웹앱을 완성하는 과정을 다뤄보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[HTTP 응답 코드 완전 정리 — 개발자가 반드시 알아야 할 상태 코드 가이드]]></title><description><![CDATA[HTTP 응답 코드 완전 정리 — 개발자가 반드시 알아야 할 상태 코드 가이드 안녕하세요, 자바파커입니다. "API에서 에러가 났는데 500만 던지고 있습니다. 어떤 코드를 써야 하나요?" 솔직히 개발 초반에는 200(성공)과 50…]]></description><link>https://blog.javapark.kr/http-status-codes-guide/</link><guid isPermaLink="false">https://blog.javapark.kr/http-status-codes-guide/</guid><pubDate>Sat, 04 Apr 2026 09:30:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/http-status-codes-guide.png&quot; alt=&quot;HTTP 응답 코드 완전 정리 — 개발자가 반드시 알아야 할 상태 코드 가이드&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;API에서 에러가 났는데 500만 던지고 있습니다. 어떤 코드를 써야 하나요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 개발 초반에는 200(성공)과 500(에러), 이 두 개만 쓰는 경우가 많습니다. 하지만 적절한 상태 코드를 사용하면 &lt;strong&gt;프론트엔드가 에러를 자동으로 처리&lt;/strong&gt;할 수 있고, &lt;strong&gt;디버깅 시간이 절반으로&lt;/strong&gt; 줄어듭니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;실무에서 자주 쓰는 코드는 15개 정도&lt;/strong&gt;입니다. 오늘은 전체 구조를 훑은 뒤, 실무에서 꼭 알아야 할 코드를 중심으로 정리하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HTTP 응답 코드 구조 — 첫 숫자가 핵심&lt;/h2&gt;
&lt;p&gt;HTTP 상태 코드는 &lt;strong&gt;3자리 숫자&lt;/strong&gt;이고, 첫 번째 숫자가 응답의 종류를 결정합니다.&lt;/p&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;th&gt;비유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1xx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;정보&lt;/td&gt;
&lt;td&gt;&quot;알겠습니다, 계속하세요&quot;&lt;/td&gt;
&lt;td&gt;전화 연결 중 &quot;잠시만요&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2xx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;성공&lt;/td&gt;
&lt;td&gt;&quot;요청을 정상 처리했습니다&quot;&lt;/td&gt;
&lt;td&gt;&quot;주문 완료되었습니다&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3xx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;리다이렉션&lt;/td&gt;
&lt;td&gt;&quot;다른 곳으로 가세요&quot;&lt;/td&gt;
&lt;td&gt;&quot;매장이 이전했습니다&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4xx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;클라이언트 에러&lt;/td&gt;
&lt;td&gt;&quot;당신의 요청에 문제가 있습니다&quot;&lt;/td&gt;
&lt;td&gt;&quot;주문서를 잘못 작성하셨습니다&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5xx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 에러&lt;/td&gt;
&lt;td&gt;&quot;서버에서 문제가 발생했습니다&quot;&lt;/td&gt;
&lt;td&gt;&quot;주방에서 불이 났습니다&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심:&lt;/strong&gt; 4xx는 &lt;strong&gt;클라이언트(요청자)의 잘못&lt;/strong&gt;, 5xx는 &lt;strong&gt;서버의 잘못&lt;/strong&gt;입니다. 이 구분을 명확히 하는 것만으로도 디버깅 시간이 크게 줄어듭니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;1xx — 정보 응답&lt;/h2&gt;
&lt;p&gt;실무에서 직접 다룰 일은 거의 없지만, 알아두면 로그 분석 시 도움이 됩니다.&lt;/p&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;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;요청 헤더를 받았으니 본문을 보내도 됩니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;101&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Switching Protocols&lt;/td&gt;
&lt;td&gt;WebSocket 전환 시 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;103&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Early Hints&lt;/td&gt;
&lt;td&gt;브라우저가 리소스를 미리 로드할 수 있도록 힌트 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;실무에서 만나는 경우&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# WebSocket 연결 시 101 응답
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;2xx — 성공&lt;/h2&gt;
&lt;p&gt;가장 많이 보고 싶은 응답 코드입니다.&lt;/p&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;th&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;요청 성공&lt;/td&gt;
&lt;td&gt;GET 조회, PUT 수정 후 결과 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;201&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Created&lt;/td&gt;
&lt;td&gt;리소스 생성 완료&lt;/td&gt;
&lt;td&gt;POST로 새 데이터 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;202&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accepted&lt;/td&gt;
&lt;td&gt;요청 접수됨 (아직 처리 중)&lt;/td&gt;
&lt;td&gt;비동기 작업 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;204&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No Content&lt;/td&gt;
&lt;td&gt;성공했지만 응답 본문 없음&lt;/td&gt;
&lt;td&gt;DELETE 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;실무 포인트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 200 — 조회 성공
GET /api/users/1
→ 200 OK
→ {&quot;id&quot;: 1, &quot;name&quot;: &quot;김철수&quot;, &quot;role&quot;: &quot;개발자&quot;}

# 201 — 생성 성공 (Location 헤더에 새 리소스 경로)
POST /api/users
→ 201 Created
→ Location: /api/users/42
→ {&quot;id&quot;: 42, &quot;name&quot;: &quot;이영희&quot;}

# 202 — 비동기 작업 접수 (이메일 발송, 대용량 처리 등)
POST /api/reports/generate
→ 202 Accepted
→ {&quot;jobId&quot;: &quot;abc123&quot;, &quot;status&quot;: &quot;processing&quot;}

# 204 — 삭제 성공 (본문 없이 응답)
DELETE /api/users/1
→ 204 No Content&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;흔한 실수:&lt;/strong&gt; 리소스를 생성(POST)하고 200을 반환하는 경우가 많습니다. &lt;strong&gt;201을 쓰는 게 표준&lt;/strong&gt;이고, 프론트엔드가 &quot;새로 만들어졌구나&quot;라고 인지할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;3xx — 리다이렉션&lt;/h2&gt;
&lt;p&gt;URL이 바뀌었거나, 다른 곳으로 안내할 때 사용합니다.&lt;/p&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;th&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;301&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moved Permanently&lt;/td&gt;
&lt;td&gt;영구 이동&lt;/td&gt;
&lt;td&gt;도메인 변경, URL 구조 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;302&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Found&lt;/td&gt;
&lt;td&gt;임시 이동&lt;/td&gt;
&lt;td&gt;로그인 후 원래 페이지로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;304&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not Modified&lt;/td&gt;
&lt;td&gt;변경 없음 (캐시 사용)&lt;/td&gt;
&lt;td&gt;브라우저 캐싱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;307&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Temporary Redirect&lt;/td&gt;
&lt;td&gt;임시 이동 (메서드 유지)&lt;/td&gt;
&lt;td&gt;HTTPS 강제 리다이렉트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;308&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permanent Redirect&lt;/td&gt;
&lt;td&gt;영구 이동 (메서드 유지)&lt;/td&gt;
&lt;td&gt;API URL 영구 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;301 vs 302 — 실무에서 가장 중요한 차이&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 301 — 영구 이동: 검색 엔진이 새 URL을 인덱싱
# SEO에서 매우 중요. 잘못 쓰면 검색 순위가 사라질 수 있음
HTTP/1.1 301 Moved Permanently
Location: https://new-domain.com/page

# 302 — 임시 이동: 검색 엔진이 기존 URL 유지
# 점검 페이지, 로그인 후 리다이렉트 등
HTTP/1.1 302 Found
Location: /login?redirect=/dashboard&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;304 — 캐시의 핵심&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 1차 요청
GET /api/data
→ 200 OK
→ ETag: &quot;abc123&quot;

# 2차 요청 (브라우저가 ETag를 보냄)
GET /api/data
If-None-Match: &quot;abc123&quot;
→ 304 Not Modified  ← 본문 없이 &quot;캐시 쓰세요&quot; 응답&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;301과 308의 차이:&lt;/strong&gt; 301은 리다이렉트 시 POST → GET으로 바뀔 수 있습니다. 308은 &lt;strong&gt;원래 메서드를 유지&lt;/strong&gt;합니다. API에서는 308이 더 안전합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;4xx — 클라이언트 에러&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&quot;당신이 잘못한 겁니다&quot;&lt;/strong&gt; — 요청 자체에 문제가 있을 때 사용합니다.&lt;/p&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;th&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;400&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bad Request&lt;/td&gt;
&lt;td&gt;요청 형식 오류&lt;/td&gt;
&lt;td&gt;필수 파라미터 누락, JSON 파싱 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;401&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unauthorized&lt;/td&gt;
&lt;td&gt;인증 필요&lt;/td&gt;
&lt;td&gt;로그인 안 됨, 토큰 만료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;403&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forbidden&lt;/td&gt;
&lt;td&gt;권한 없음&lt;/td&gt;
&lt;td&gt;로그인은 됐지만 접근 권한 부족&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;404&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not Found&lt;/td&gt;
&lt;td&gt;리소스 없음&lt;/td&gt;
&lt;td&gt;존재하지 않는 URL/ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;405&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Method Not Allowed&lt;/td&gt;
&lt;td&gt;메서드 불허&lt;/td&gt;
&lt;td&gt;GET만 허용되는 URL에 POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;408&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Request Timeout&lt;/td&gt;
&lt;td&gt;요청 시간 초과&lt;/td&gt;
&lt;td&gt;클라이언트 응답이 너무 늦음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;409&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;충돌&lt;/td&gt;
&lt;td&gt;중복 데이터, 동시 수정 충돌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;413&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Payload Too Large&lt;/td&gt;
&lt;td&gt;요청 본문 초과&lt;/td&gt;
&lt;td&gt;파일 업로드 크기 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;415&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unsupported Media Type&lt;/td&gt;
&lt;td&gt;미지원 타입&lt;/td&gt;
&lt;td&gt;JSON 기대했는데 XML이 옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;422&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unprocessable Entity&lt;/td&gt;
&lt;td&gt;처리 불가&lt;/td&gt;
&lt;td&gt;형식은 맞지만 비즈니스 로직 위반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;429&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Too Many Requests&lt;/td&gt;
&lt;td&gt;요청 과다&lt;/td&gt;
&lt;td&gt;Rate Limit 초과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;401 vs 403 — 가장 많이 혼동하는 코드&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 401 — &quot;누구세요?&quot; (인증 실패)
# 로그인하지 않았거나 토큰이 만료됨
GET /api/profile
→ 401 Unauthorized
→ {&quot;error&quot;: &quot;토큰이 만료되었습니다. 다시 로그인해주세요.&quot;}

# 403 — &quot;당신은 안 됩니다&quot; (인가 실패)
# 로그인은 했지만 해당 리소스에 접근 권한이 없음
GET /api/admin/settings
→ 403 Forbidden
→ {&quot;error&quot;: &quot;관리자 권한이 필요합니다.&quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;401 Unauthorized&lt;/th&gt;
&lt;th&gt;403 Forbidden&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인증(Authentication)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실패&lt;/td&gt;
&lt;td&gt;성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인가(Authorization)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;확인 불가&lt;/td&gt;
&lt;td&gt;실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결법&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로그인/토큰 갱신&lt;/td&gt;
&lt;td&gt;권한 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;400 vs 422 — 미묘한 차이&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 400 — 요청 자체가 깨짐 (파싱 불가)
POST /api/users
Content-Type: application/json
Body: {invalid json...
→ 400 Bad Request
→ {&quot;error&quot;: &quot;JSON 파싱에 실패했습니다.&quot;}

# 422 — 형식은 맞지만 내용이 유효하지 않음
POST /api/users
Body: {&quot;email&quot;: &quot;not-an-email&quot;, &quot;age&quot;: -5}
→ 422 Unprocessable Entity
→ {&quot;errors&quot;: [
    {&quot;field&quot;: &quot;email&quot;, &quot;message&quot;: &quot;올바른 이메일 형식이 아닙니다&quot;},
    {&quot;field&quot;: &quot;age&quot;, &quot;message&quot;: &quot;0 이상이어야 합니다&quot;}
  ]}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;429 — Rate Limiting&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# API 호출 제한 초과
GET /api/search?q=test
→ 429 Too Many Requests
→ Retry-After: 30        ← 30초 후 재시도
→ X-RateLimit-Limit: 100
→ X-RateLimit-Remaining: 0
→ {&quot;error&quot;: &quot;요청 제한을 초과했습니다. 30초 후 다시 시도해주세요.&quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;5xx — 서버 에러&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&quot;우리가 잘못한 겁니다&quot;&lt;/strong&gt; — 서버 측 문제로 요청을 처리하지 못할 때 사용합니다.&lt;/p&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;th&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal Server Error&lt;/td&gt;
&lt;td&gt;서버 내부 오류&lt;/td&gt;
&lt;td&gt;예기치 않은 예외, 버그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;502&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bad Gateway&lt;/td&gt;
&lt;td&gt;게이트웨이 오류&lt;/td&gt;
&lt;td&gt;업스트림 서버 연결 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;503&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Service Unavailable&lt;/td&gt;
&lt;td&gt;서비스 불가&lt;/td&gt;
&lt;td&gt;서버 점검, 과부하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;504&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gateway Timeout&lt;/td&gt;
&lt;td&gt;게이트웨이 시간 초과&lt;/td&gt;
&lt;td&gt;업스트림 서버 응답 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;502 vs 503 vs 504 — 어디서 문제인지 파악하기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;클라이언트 → [Nginx/ALB] → [애플리케이션 서버] → [DB]
                 │
                 ├── 502: 앱 서버가 죽었거나 잘못된 응답 반환
                 ├── 503: 앱 서버가 점검 중이거나 과부하
                 └── 504: 앱 서버가 응답을 너무 늦게 줌&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 502 — 앱 서버 프로세스가 죽은 경우
→ 502 Bad Gateway
→ Nginx 로그: &quot;upstream prematurely closed connection&quot;

# 503 — 점검 모드 (Retry-After 헤더 포함)
→ 503 Service Unavailable
→ Retry-After: 3600
→ {&quot;error&quot;: &quot;서버 점검 중입니다. 1시간 후 다시 시도해주세요.&quot;}

# 504 — DB 쿼리가 30초 넘게 걸린 경우
→ 504 Gateway Timeout
→ Nginx 로그: &quot;upstream timed out (110: Connection timed out)&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;중요:&lt;/strong&gt; 500 에러에 내부 스택 트레이스를 노출하지 마세요. 보안 취약점이 됩니다. 로그에는 상세 정보를 남기되, 응답에는 일반적인 메시지만 반환하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;REST API 설계 시 상태 코드 선택 가이드&lt;/h2&gt;
&lt;p&gt;어떤 상황에 어떤 코드를 써야 하는지 빠르게 참고할 수 있는 가이드입니다.&lt;/p&gt;
&lt;h3&gt;CRUD별 권장 코드&lt;/h3&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;th&gt;실패 (서버)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GET&lt;/strong&gt; (조회)&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;404 (없음), 400 (파라미터 오류)&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;POST&lt;/strong&gt; (생성)&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;400 (형식 오류), 409 (중복), 422 (유효성)&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PUT&lt;/strong&gt; (전체 수정)&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;404 (없음), 400, 422&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PATCH&lt;/strong&gt; (부분 수정)&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;404, 400, 422&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DELETE&lt;/strong&gt; (삭제)&lt;/td&gt;
&lt;td&gt;204&lt;/td&gt;
&lt;td&gt;404 (없음)&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;인증·인가 흐름&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;요청 →  토큰 없음?         → 401 Unauthorized
    →  토큰 만료?          → 401 Unauthorized
    →  토큰 유효, 권한 없음? → 403 Forbidden
    →  토큰 유효, 권한 있음  → 200/201/204...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;에러 응답 본문 표준 형식&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;422&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Unprocessable Entity&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;입력 데이터가 유효하지 않습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;details&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;올바른 이메일 형식이 아닙니다&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0 이상이어야 합니다&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-04-04T09:30:00Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/api/users&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;실무에서 꼭 기억할 코드 TOP 10&lt;/h2&gt;
&lt;p&gt;모든 코드를 외울 필요는 없습니다. 이 10개만 정확히 사용해도 대부분의 상황을 커버합니다.&lt;/p&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;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;201&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;생성 성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;204&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;성공, 본문 없음 (삭제)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;400&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;잘못된 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;401&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;인증 필요 (로그인 안 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;403&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;권한 없음 (로그인은 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;404&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;찾을 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;409&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;충돌 (중복)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;422&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;유효성 검증 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 에러&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ — 자주 묻는 질문&lt;/h2&gt;
&lt;h3&gt;Q. 200과 204 중 어떤 걸 써야 하나요?&lt;/h3&gt;
&lt;p&gt;응답 본문이 있으면 &lt;strong&gt;200&lt;/strong&gt;, 없으면 &lt;strong&gt;204&lt;/strong&gt;입니다. DELETE 후 삭제된 데이터를 반환하고 싶으면 200, 그냥 &quot;삭제됨&quot;만 알리려면 204를 사용합니다. 팀 내 컨벤션을 정하고 일관되게 사용하는 게 가장 중요합니다.&lt;/p&gt;
&lt;h3&gt;Q. 에러 시 200 + 에러 메시지를 반환하면 안 되나요?&lt;/h3&gt;
&lt;p&gt;기술적으로는 가능하지만 &lt;strong&gt;안티패턴&lt;/strong&gt;입니다. HTTP 클라이언트, 프록시, 모니터링 도구는 모두 상태 코드를 기준으로 성공/실패를 판단합니다. 200으로 에러를 반환하면 모니터링에서 에러가 잡히지 않고, 프론트엔드도 매번 본문을 파싱해야 합니다.&lt;/p&gt;
&lt;h3&gt;Q. 404와 410의 차이는?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;404&lt;/strong&gt;는 &quot;없음 (있었는지도 모름)&quot;, &lt;strong&gt;410 Gone&lt;/strong&gt;은 &quot;있었는데 삭제됨 (다시 안 돌아옴)&quot;입니다. 검색 엔진에 &quot;이 URL은 영구 삭제됐으니 인덱스에서 빼라&quot;고 알리고 싶을 때 410을 사용합니다. 일반 API에서는 404면 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;HTTP 상태 코드는 서버와 클라이언트가 대화하는 &lt;strong&gt;공통 언어&lt;/strong&gt;입니다. 적절한 코드를 사용하면 프론트엔드 개발자가 별도 문서 없이도 에러를 처리할 수 있고, 모니터링 도구가 자동으로 문제를 감지할 수 있습니다.&lt;/p&gt;
&lt;p&gt;처음에는 TOP 10만 정확히 사용하는 것부터 시작하세요. 그것만으로도 API 품질이 크게 올라갑니다.&lt;/p&gt;
&lt;p&gt;여러분의 API에서는 어떤 상태 코드를 주로 사용하고 계신가요? 실무에서 겪은 재미있는 에러 코드 경험이 있다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[아이디어 검증부터 시작하기 — AI와 함께 기획하는 법]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "아이디어는 넘치는데, 뭘 만들어야 할지 모르겠다."…]]></description><link>https://blog.javapark.kr/01-idea-validation/</link><guid isPermaLink="false">https://blog.javapark.kr/01-idea-validation/</guid><pubDate>Sat, 04 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;아이디어는 넘치는데, 뭘 만들어야 할지 모르겠다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1인 개발자라면 한 번쯤 이런 고민을 해보셨을 겁니다. 저도 그랬고, 솔직히 지금도 그렇습니다. 문제는 아이디어가 없는 게 아니라, &lt;strong&gt;어떤 아이디어가 실제로 만들 가치가 있는지&lt;/strong&gt; 판단하기 어렵다는 점이죠.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — AI를 기획 파트너로 활용하면, 혼자서도 놀라울 정도로 체계적인 기획이 가능합니다. 이번 포스팅에서는 &lt;strong&gt;아이디어 발굴부터 PRD 작성까지&lt;/strong&gt;, AI와 함께하는 1인 개발자의 기획 프로세스를 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1인 개발의 현실 — 왜 기획이 가장 중요한가&lt;/h2&gt;
&lt;p&gt;1인 개발에서 가장 무서운 건 &quot;3개월 동안 만들었는데, 아무도 안 쓴다&quot;는 상황입니다. 대기업이라면 기획자, 디자이너, PM이 각각의 역할을 맡지만, 1인 개발자는 이 모든 걸 혼자 해야 합니다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;strong&gt;기획 단계에서의 검증&lt;/strong&gt;이 더욱 중요합니다.&lt;/p&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;1인 개발자&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;아이디어&lt;/td&gt;
&lt;td&gt;기획팀 브레인스토밍&lt;/td&gt;
&lt;td&gt;혼자 고민&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시장 조사&lt;/td&gt;
&lt;td&gt;리서치팀 보고서&lt;/td&gt;
&lt;td&gt;구글링 + 감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRD 작성&lt;/td&gt;
&lt;td&gt;PM이 작성&lt;/td&gt;
&lt;td&gt;건너뛰기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MVP 정의&lt;/td&gt;
&lt;td&gt;스프린트 플래닝&lt;/td&gt;
&lt;td&gt;&quot;일단 다 만들자&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 표를 보면 느끼셨겠지만, 1인 개발자의 기획은 대부분 &lt;strong&gt;감에 의존&lt;/strong&gt;합니다. 하지만 AI를 활용하면 이 격차를 상당 부분 좁힐 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;아이디어 브레인스토밍 — AI와 대화하며 아이디어 발굴하기&lt;/h2&gt;
&lt;h3&gt;첫 번째 프롬프트: 넓게 펼치기&lt;/h3&gt;
&lt;p&gt;아이디어가 막연할 때는, AI에게 &lt;strong&gt;제약 조건과 함께 아이디어를 요청&lt;/strong&gt;하는 것이 효과적입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;나는 1인 개발자야. 아래 조건에 맞는 사이드 프로젝트 아이디어를 10개 제안해줘.

조건:
- 혼자서 2주 안에 MVP를 만들 수 있는 규모
- Next.js + Supabase로 구현 가능
- B2C 또는 개인용 도구
- 월 구독 모델 또는 광고 수익 가능
- 이미 레드오션인 분야는 제외

각 아이디어마다 다음을 포함해줘:
1. 서비스명 (가칭)
2. 한 줄 설명
3. 타겟 사용자
4. 수익 모델
5. 기술적 난이도 (상/중/하)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 구체적인 제약 조건을 주면, AI가 훨씬 현실적인 아이디어를 제안합니다. &quot;앱 아이디어 추천해줘&quot;라고 물어보면 뻔한 답이 나오지만, &lt;strong&gt;조건을 좁히면 좁힐수록 실용적인 결과&lt;/strong&gt;가 나옵니다.&lt;/p&gt;
&lt;h3&gt;두 번째 프롬프트: 깊게 파기&lt;/h3&gt;
&lt;p&gt;10개 아이디어 중 마음에 드는 2~3개를 골랐다면, 각각에 대해 더 깊이 파봅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;독서 기록 웹앱&quot; 아이디어를 더 구체화해줘.

다음 질문에 답해줘:
1. 기존 서비스(밀리의 서재, 리디 독서노트, 북적북적)와 차별점은?
2. 핵심 기능 3가지는?
3. 사용자가 이 앱을 매일 열어볼 이유는?
4. 기술적으로 가장 까다로운 부분은?
5. 2주 안에 만들 수 있는 MVP 범위는?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 단계에서 중요한 건, AI의 답변을 &lt;strong&gt;그대로 수용하지 않는 것&lt;/strong&gt;입니다. AI는 아이디어를 정리하고 구조화하는 데 탁월하지만, &quot;이게 정말 사람들이 원하는 건가?&quot;라는 판단은 결국 본인의 몫입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;시장 조사 — AI로 경쟁 서비스 분석하고 타겟 사용자 정의하기&lt;/h2&gt;
&lt;h3&gt;경쟁 서비스 분석 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;한국에서 사용 가능한 독서 기록/관리 서비스를 조사해줘.

각 서비스에 대해 다음을 정리해줘:
- 서비스명과 URL
- 핵심 기능
- 가격 정책
- 장점과 단점
- 사용자 리뷰에서 자주 언급되는 불만 사항

마지막에 이 시장의 전체적인 트렌드와 비어있는 틈새(gap)를 분석해줘.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AI가 제공하는 정보는 학습 데이터 시점까지의 내용이므로, &lt;strong&gt;최신 정보는 직접 확인&lt;/strong&gt;해야 합니다. 하지만 초기 리서치의 뼈대를 잡는 데는 충분합니다.&lt;/p&gt;
&lt;h3&gt;타겟 사용자 페르소나 만들기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;독서 기록 웹앱의 타겟 사용자 페르소나를 3개 만들어줘.

각 페르소나에 포함할 항목:
- 이름 (가상)
- 나이, 직업
- 독서 습관 (월 몇 권, 선호 장르)
- 현재 독서 기록 방법
- 불편한 점 (Pain Point)
- 우리 앱에서 기대하는 것&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;페르소나를 만들어두면, 이후 기능 우선순위를 정할 때 &quot;&lt;strong&gt;이 기능이 민수(페르소나)에게 정말 필요한가?&lt;/strong&gt;&quot;라는 기준으로 판단할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;PRD 작성 — AI가 만들어주는 제품 요구사항 문서&lt;/h2&gt;
&lt;p&gt;PRD(Product Requirements Document)는 &quot;무엇을 만들 것인가&quot;를 정리한 문서입니다. 1인 개발자에게는 과하게 느껴질 수 있지만, &lt;strong&gt;PRD 없이 코딩을 시작하면 중간에 방향을 잃기 쉽습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;PRD 생성 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;독서 기록 웹앱의 PRD를 작성해줘.

서비스 개요:
- 읽은 책을 기록하고, 독서 메모를 남기고, 읽은 책 통계를 볼 수 있는 웹앱
- ISBN 바코드 스캔으로 책 정보 자동 입력
- 월간/연간 독서 통계 대시보드

PRD에 포함할 섹션:
1. 프로젝트 개요 (목적, 배경)
2. 타겟 사용자
3. 핵심 기능 목록 (우선순위 P0/P1/P2)
4. 비기능 요구사항 (성능, 보안)
5. 기술 스택
6. 마일스톤 (2주 스프린트 기준)
7. 성공 지표 (KPI)
8. 리스크 및 대응 방안&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;AI가 생성한 PRD 예시 (일부)&lt;/h3&gt;
&lt;p&gt;아래는 실제로 Claude에게 요청해서 받은 PRD의 일부입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; 독서 기록 웹앱 PRD&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 1. 프로젝트 개요&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; 1.1 목적&lt;/span&gt;

독서 애호가들이 읽은 책을 쉽게 기록하고, 독서 습관을 추적할 수 있는
웹 애플리케이션을 개발한다.

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;###&lt;/span&gt; 1.2 배경&lt;/span&gt;

기존 독서 기록 앱들은 대부분 모바일 중심이며,
웹에서의 사용 경험이 부족하다. 또한 독서 통계 기능이
단순 권수 집계에 그치는 경우가 많다.

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 2. 핵심 기능&lt;/span&gt;

&lt;span class=&quot;token table&quot;&gt;&lt;span class=&quot;token table-header-row&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 우선순위 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 기능           &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-header important&quot;&gt; 설명                            &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token table-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;--------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;--------------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-------------------------------&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token table-data-rows&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P0       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 책 등록        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; ISBN 검색으로 책 정보 자동 입력 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P0       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 독서 상태 관리 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 읽는 중/읽은 책/읽고 싶은 책    &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P0       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 독서 메모      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 책별 메모 및 인용구 저장        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P1       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 독서 통계      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 월간/연간 독서량, 장르별 분포   &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P1       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 바코드 스캔    &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 카메라로 ISBN 바코드 인식       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P2       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 소셜 공유      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 독서 기록 공개 프로필           &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; P2       &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 독서 목표      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token table-data&quot;&gt; 연간 독서 목표 설정 및 추적     &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 PRD를 기반으로 개발을 진행하면, &quot;&lt;strong&gt;다음에 뭘 해야 하지?&lt;/strong&gt;&quot;라는 고민 없이 체계적으로 진행할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;MVP 범위 정하기 — &quot;이것만 만들자&quot; 전략&lt;/h2&gt;
&lt;p&gt;1인 개발자의 가장 큰 적은 **스코프 크리프(Scope Creep)**입니다. &quot;이 기능도 넣으면 좋겠는데...&quot;를 반복하다 보면, 영영 출시하지 못합니다.&lt;/p&gt;
&lt;h3&gt;MVP 범위 확정 프롬프트&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;위 PRD 기반으로, 2주 안에 혼자서 만들 수 있는 MVP 범위를 정해줘.

조건:
- P0 기능만 포함
- 디자인은 최소한 (Tailwind CSS 기본 컴포넌트)
- 인증은 소셜 로그인(Google)만
- 배포는 Vercel
- 바코드 스캔은 MVP에서 제외 (수동 ISBN 입력으로 대체)

결과물:
1. MVP 기능 목록
2. 2주 스프린트 일정표 (일별)
3. 각 기능의 예상 소요 시간&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AI가 만들어준 일정표를 보면 이런 식입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Week 1:
- Day 1-2: 프로젝트 세팅 + Supabase 연동 + 인증
- Day 3-4: 책 등록 (ISBN 검색 API 연동)
- Day 5: 독서 상태 관리 (읽는 중/완료/위시리스트)

Week 2:
- Day 1-2: 독서 메모 CRUD
- Day 3: 기본 대시보드 (독서 목록 뷰)
- Day 4: UI 다듬기 + 반응형
- Day 5: 배포 + 버그 수정&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심은 &lt;strong&gt;과감하게 빼는 것&lt;/strong&gt;입니다. 통계 대시보드, 바코드 스캔, 소셜 공유 — 이런 건 다 v2에서 하면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시: &quot;독서 기록 웹앱&quot; 기획 과정 전체를 AI와 함께&lt;/h2&gt;
&lt;p&gt;이 시리즈에서는 &quot;&lt;strong&gt;독서 기록 웹앱&lt;/strong&gt;&quot;을 실제로 만들어보겠습니다. 위에서 설명한 프로세스를 그대로 따라가면서, 각 단계에서 AI를 어떻게 활용했는지 보여드리겠습니다.&lt;/p&gt;
&lt;h3&gt;전체 기획 흐름 요약&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. 아이디어 발굴
   └─ AI에게 사이드 프로젝트 아이디어 10개 요청
   └─ &quot;독서 기록 웹앱&quot; 선택

2. 아이디어 검증
   └─ 경쟁 서비스 5개 분석
   └─ 차별점 정의: &quot;웹 기반 + 깔끔한 통계 + 빠른 기록&quot;

3. 타겟 사용자 정의
   └─ 페르소나 3개 작성
   └─ 핵심 타겟: &quot;월 2-4권 읽는 20-30대 직장인&quot;

4. PRD 작성
   └─ P0/P1/P2 기능 분류
   └─ 기술 스택 확정: Next.js + Supabase + Vercel

5. MVP 범위 확정
   └─ P0 기능만 포함한 2주 스프린트
   └─ v1.0 출시 목표&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;기획에 소요된 시간&lt;/h3&gt;
&lt;p&gt;놀라운 건, 이 전체 과정이 &lt;strong&gt;약 2시간&lt;/strong&gt;이면 끝난다는 점입니다. AI 없이 혼자 했다면 최소 1-2일은 걸렸을 겁니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;AI 없이&lt;/th&gt;
&lt;th&gt;AI와 함께&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;아이디어 브레인스토밍&lt;/td&gt;
&lt;td&gt;3시간&lt;/td&gt;
&lt;td&gt;20분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;경쟁 서비스 조사&lt;/td&gt;
&lt;td&gt;4시간&lt;/td&gt;
&lt;td&gt;30분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;페르소나 작성&lt;/td&gt;
&lt;td&gt;2시간&lt;/td&gt;
&lt;td&gt;15분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRD 작성&lt;/td&gt;
&lt;td&gt;4시간&lt;/td&gt;
&lt;td&gt;30분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MVP 범위 확정&lt;/td&gt;
&lt;td&gt;2시간&lt;/td&gt;
&lt;td&gt;15분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;합계&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15시간&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;약 2시간&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;물론 AI가 만든 결과물을 &lt;strong&gt;검토하고 수정하는 시간&lt;/strong&gt;은 별도입니다. 하지만 백지에서 시작하는 것과 초안을 수정하는 것은 완전히 다른 난이도죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기획 단계에서 AI를 잘 활용하는 팁 3가지&lt;/h2&gt;
&lt;h3&gt;1. 구체적인 제약 조건을 주세요&lt;/h3&gt;
&lt;p&gt;&quot;앱 아이디어 추천해줘&quot;보다 &quot;Next.js + Supabase로 2주 안에 만들 수 있는 B2C 웹앱 아이디어&quot;가 훨씬 좋은 결과를 냅니다.&lt;/p&gt;
&lt;h3&gt;2. 대화를 이어가세요&lt;/h3&gt;
&lt;p&gt;한 번의 프롬프트로 끝내지 말고, AI의 답변에 대해 **&quot;왜?&quot;, &quot;좀 더 구체적으로&quot;, &quot;다른 관점에서&quot;**라고 계속 질문하세요. 대화가 깊어질수록 결과물의 질이 올라갑니다.&lt;/p&gt;
&lt;h3&gt;3. AI의 답변을 맹신하지 마세요&lt;/h3&gt;
&lt;p&gt;AI는 기획의 &lt;strong&gt;도구&lt;/strong&gt;이지, 기획자가 아닙니다. 최종 판단은 항상 본인이 해야 합니다. 특히 시장 데이터는 반드시 직접 확인하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q1. ChatGPT와 Claude 중 기획에는 어떤 걸 쓰는 게 좋나요?&lt;/h3&gt;
&lt;p&gt;솔직히 말하면 기획 단계에서는 큰 차이가 없습니다. 다만 &lt;strong&gt;긴 문서(PRD) 작성&lt;/strong&gt;에는 Claude가 더 강하고, &lt;strong&gt;아이디어 브레인스토밍&lt;/strong&gt;에는 ChatGPT가 살짝 더 창의적인 편입니다. 저는 둘 다 번갈아 쓰면서 결과를 비교합니다.&lt;/p&gt;
&lt;h3&gt;Q2. AI가 만든 PRD를 그대로 써도 되나요?&lt;/h3&gt;
&lt;p&gt;초안으로는 충분합니다. 하지만 반드시 &lt;strong&gt;본인의 상황에 맞게 수정&lt;/strong&gt;해야 합니다. AI는 &quot;2주 안에 만들 수 있다&quot;고 했지만, 본인의 실력과 가용 시간에 따라 일정은 달라질 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q3. 아이디어 검증을 AI로만 해도 충분한가요?&lt;/h3&gt;
&lt;p&gt;아닙니다. AI를 통한 검증은 &lt;strong&gt;1차 필터&lt;/strong&gt; 정도로 생각하세요. 실제 타겟 사용자에게 직접 물어보는 것이 가장 확실한 검증 방법입니다. 주변 지인이나 온라인 커뮤니티에 &quot;이런 앱 있으면 쓸 건가요?&quot;라고 물어보는 것만으로도 큰 인사이트를 얻을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 사이드 프로젝트 기획할 때 AI를 어떻게 활용하고 계신가요? 댓글로 여러분만의 노하우를 공유해주세요!&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 &quot;&lt;strong&gt;설계는 AI에게 맡겨라 — ERD, API, 와이어프레임 자동화&lt;/strong&gt;&quot;를 주제로, Claude Code를 활용한 설계 프로세스를 다뤄보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[COKACDIR — 텔레그램으로 Claude Code, Codex CLI를 원격 제어하는 법]]></title><description><![CDATA[COKACDIR 텔레그램 AI 코딩 에이전트 원격 제어 커버 — Claude Code·Codex CLI 모바일 제어 안녕하세요, 자바파커입니다. "Claude Code를 쓰고 싶은데, 항상 컴퓨터 앞에 앉아 있을 수는 없잖아요." AI…]]></description><link>https://blog.javapark.kr/cokacdir-telegram-ai-coding-agent/</link><guid isPermaLink="false">https://blog.javapark.kr/cokacdir-telegram-ai-coding-agent/</guid><pubDate>Wed, 01 Apr 2026 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/cokacdir-telegram-ai-coding-agent.png&quot; alt=&quot;COKACDIR 텔레그램 AI 코딩 에이전트 원격 제어 커버 — Claude Code·Codex CLI 모바일 제어&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Claude Code를 쓰고 싶은데, 항상 컴퓨터 앞에 앉아 있을 수는 없잖아요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI 코딩 에이전트를 써보면 한 가지 아쉬운 점이 있습니다. &lt;strong&gt;내 컴퓨터 터미널에서만 쓸 수 있다&lt;/strong&gt;는 것. 외출 중에 &quot;아 그 버그 고쳐야 하는데&quot; 싶어도, 노트북을 열 수 없으면 답이 없습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;COKACDIR을 쓰면 텔레그램 메시지 하나로 내 컴퓨터의 Claude Code를 원격 조작할 수 있습니다.&lt;/strong&gt; 새로운 AI를 설치하는 게 아니라, 이미 쓰고 있는 코딩 에이전트를 텔레그램에서 리모컨처럼 조종하는 방식입니다.&lt;/p&gt;
&lt;p&gt;저는 실제로 사내에서 봇 2개를 만들어 각각 Claude Code와 Codex CLI를 연결하고, 하나의 그룹에서 &quot;작성 → 검증&quot; 역할을 나눠 운영하고 있습니다. 직접 써본 경험까지 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;COKACDIR이 뭔가?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;COKACDIR&lt;/strong&gt;(코칵디르)은 기존 AI 코딩 에이전트를 텔레그램에서 원격 제어하게 해주는 오픈소스 도구입니다.&lt;/p&gt;
&lt;p&gt;핵심 컨셉을 정리하면:&lt;/p&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;&lt;strong&gt;정체&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI 코딩 에이전트 원격 제어 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지원 에이전트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code, Codex CLI, Gemini CLI, OpenCode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;조작 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;텔레그램 봇&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;기술 스택&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rust 74.7%, TypeScript 22.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;바이너리 크기&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15~20MB (단일 바이너리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;라이선스&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/kstost/cokacdir&quot;&gt;kstost/cokacdir&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;비유하자면&lt;/strong&gt; — Claude Code가 내 컴퓨터에서 일하는 AI 개발자라면, COKACDIR은 그 개발자에게 &lt;strong&gt;텔레그램으로 업무 지시를 보내는 채널&lt;/strong&gt;입니다. AI 자체가 아니라, 기존 AI에 리모컨을 달아주는 도구입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 필요한가?&lt;/h2&gt;
&lt;h3&gt;기존 방식의 한계&lt;/h3&gt;
&lt;p&gt;Claude Code, Codex CLI 같은 코딩 에이전트는 강력하지만, &lt;strong&gt;내 컴퓨터 터미널에 직접 접속해야&lt;/strong&gt; 합니다.&lt;/p&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;COKACDIR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;외출 중 긴급 버그 수정&lt;/td&gt;
&lt;td&gt;노트북 필요&lt;/td&gt;
&lt;td&gt;텔레그램에서 지시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장시간 작업 모니터링&lt;/td&gt;
&lt;td&gt;터미널 열어둬야 함&lt;/td&gt;
&lt;td&gt;텔레그램으로 실시간 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예약 작업 (야간 빌드 등)&lt;/td&gt;
&lt;td&gt;별도 CI/CD 구성&lt;/td&gt;
&lt;td&gt;cron 표현식으로 예약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;팀원과 AI 공유&lt;/td&gt;
&lt;td&gt;각자 설치 필요&lt;/td&gt;
&lt;td&gt;그룹 채팅에서 공유 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;특히 &lt;strong&gt;스마트폰 하나로&lt;/strong&gt; AI 코딩 에이전트를 제어할 수 있다는 점이 핵심입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치 방법 — 5분 가이드&lt;/h2&gt;
&lt;h3&gt;Step 1: COKACDIR 설치&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-fsSL&lt;/span&gt; https://cokacdir.cokac.com/manage.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; cokacctl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Windows (관리자 PowerShell):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;irm&lt;/span&gt; https:&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;cokacdir&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cokac&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;com/manage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;iex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; cokacctl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설치하면 &lt;code class=&quot;language-text&quot;&gt;cokacctl&lt;/code&gt;이라는 관리 TUI가 열립니다.&lt;/p&gt;
&lt;h3&gt;Step 2: 텔레그램 봇 생성&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;텔레그램에서 &lt;strong&gt;@BotFather&lt;/strong&gt;를 검색&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/newbot&lt;/code&gt; 명령 입력&lt;/li&gt;
&lt;li&gt;봇 이름과 아이디 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 토큰&lt;/strong&gt;을 복사 (예: &lt;code class=&quot;language-text&quot;&gt;123456789:ABCdefGhIjKlmNoPqRsTuVwXyZ&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 3: COKACDIR 설정&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;cokacctl&lt;/code&gt; TUI에서:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;i&lt;/code&gt; 키 — 설치&lt;/li&gt;
&lt;li&gt;복사한 텔레그램 봇 토큰 입력&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;s&lt;/code&gt; 키 — 서버 시작&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 3단계면 끝입니다. 이제 텔레그램에서 내가 만든 봇에게 메시지를 보내면, 내 컴퓨터의 코딩 에이전트가 작업을 수행합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;주요 기능&lt;/h2&gt;
&lt;h3&gt;1. 텔레그램에서 코딩 에이전트 제어&lt;/h3&gt;
&lt;p&gt;텔레그램 봇에게 자연어로 메시지를 보내면, 연결된 코딩 에이전트(Claude Code 등)가 실행합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;나: src/api/auth.js에서 JWT 만료 시간을 1시간에서 24시간으로 변경해줘

봇: [Claude Code 실행 중...]
     auth.js 파일을 수정했습니다.
     - expiresIn: &apos;1h&apos; → &apos;24h&apos;
     변경 완료.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실시간 스트리밍으로 진행 상황을 확인할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;2. 멀티 에이전트 지원&lt;/h3&gt;
&lt;p&gt;하나의 COKACDIR에서 여러 코딩 에이전트를 전환할 수 있습니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;/model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용할 에이전트/모델 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;세션 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;세션 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;세션 중지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/clear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;대화 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3. 예약 작업&lt;/h3&gt;
&lt;p&gt;cron 표현식이나 절대 시간으로 작업을 예약할 수 있습니다. 야간 빌드, 정기 코드 점검 같은 반복 작업에 유용합니다.&lt;/p&gt;
&lt;h3&gt;4. 파일 업로드/다운로드&lt;/h3&gt;
&lt;p&gt;텔레그램으로 파일을 보내면 서버에 업로드되고, &lt;code class=&quot;language-text&quot;&gt;/down&lt;/code&gt; 명령으로 서버의 파일을 텔레그램으로 받을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;5. 보안 기능&lt;/h3&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;&lt;strong&gt;AES-256 암호화&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;설정 가능한 청크 분할로 파일 암호화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;접근 제어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/allowed&lt;/code&gt;로 허용 사용자 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;도구 제한&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/allowedtools&lt;/code&gt;로 사용 가능한 도구 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;추가 기능 — 코딩 에이전트 외&lt;/h2&gt;
&lt;p&gt;COKACDIR은 원격 코딩 제어 외에도 부가 기능을 제공합니다.&lt;/p&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;&lt;strong&gt;터미널 멀티패널&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;분할 화면으로 여러 작업 동시 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;20+ 언어 구문 강조&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;코드 파일 내장 뷰어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;이미지 뷰어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kitty, iTerm2, Sixel 프로토콜 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git 통합&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;상태, 커밋, 로그, 브랜치 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSH/SFTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;원격 서버 접속&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;중복 파일 감지&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;디스크 정리 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;프로세스 관리자&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;실행 중인 프로세스 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;지원 플랫폼&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS&lt;/th&gt;
&lt;th&gt;아키텍처&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apple Silicon (ARM64), Intel (x86_64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;x86_64, ARM64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;x86_64, ARM64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rust로 작성되어 단일 바이너리(15~20MB)로 배포됩니다. 별도 런타임 설치 없이 바로 실행할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 활용 — 직접 써본 경험&lt;/h2&gt;
&lt;h3&gt;멀티봇 협업 구조: AI 2대를 한 그룹에서 굴리기&lt;/h3&gt;
&lt;p&gt;저는 사내에서 &lt;strong&gt;COKACDIR 봇 2개&lt;/strong&gt;를 운영하고 있습니다. 각 봇에 서로 다른 코딩 에이전트를 연결하고, 하나의 텔레그램 그룹에 초대해서 &lt;strong&gt;역할을 분리&lt;/strong&gt;했습니다.&lt;/p&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;&lt;strong&gt;Bot A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;코드 작성, 아키텍처 설계, 리팩토링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bot B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Codex CLI / Gemini CLI&lt;/td&gt;
&lt;td&gt;코드 리뷰, 테스트 작성, 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;작업 흐름은 이렇습니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[텔레그램 그룹]

나: @BotA 사용자 인증 API를 만들어줘.
    JWT 기반, refresh token 포함.

Bot A (Claude Code):
    인증 API를 작성했습니다.
    - POST /auth/login
    - POST /auth/refresh
    - POST /auth/logout
    파일 3개 생성 완료.

나: @BotB 방금 Bot A가 만든 인증 API 코드를 리뷰해줘.
    보안 취약점이 있는지 확인하고, 테스트도 작성해줘.

Bot B (Codex CLI):
    리뷰 완료. 2가지 개선사항:
    1. refresh token 저장 시 해싱 필요
    2. rate limiting 미적용
    테스트 파일 생성 완료 (12 cases).

나: @BotA 리뷰 반영해서 수정해줘.

Bot A: 수정 완료. 모든 테스트 통과.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;핵심은 &quot;작성 → 검증 → 수정&quot;을 서로 다른 AI가 맡는다는 점&lt;/strong&gt;입니다. 한 AI가 만든 코드를 다른 AI가 검증하면, 한 AI에게 모든 걸 맡기는 것보다 완성도가 확실히 올라갑니다. 사람이 하는 코드 리뷰 프로세스와 똑같은 구조를 AI끼리 돌리는 셈입니다.&lt;/p&gt;
&lt;h3&gt;이 구조의 장점&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;크로스 체크&lt;/strong&gt;: AI도 실수합니다. 다른 모델이 검증하면 놓치는 부분을 잡아냅니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;역할 특화&lt;/strong&gt;: Claude Code는 코드 생성이 강하고, Codex는 테스트 작성에 능합니다. 각자 잘하는 일을 맡기면 효율적입니다&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;/ol&gt;
&lt;h3&gt;기타 활용 시나리오&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;외출 중 긴급 핫픽스:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;나: production에서 /api/users 500 에러. 로그 확인하고 수정해줘.
봇: null 참조 에러 확인. 수정 완료. 커밋할까요?
나: 응
봇: 푸시 완료.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;야간 자동 작업:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;cron 표현식으로 매일 새벽 2시에 테스트를 실행하고 결과를 텔레그램으로 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;팀 공유:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;텔레그램 그룹에 봇을 추가하면 팀원 모두가 AI 에이전트를 사용할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;/allowed&lt;/code&gt; 명령으로 접근 권한을 관리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;주의할 점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;보안&lt;/strong&gt;: 내 컴퓨터에서 AI가 직접 실행되므로, 봇 토큰 관리와 접근 제어(&lt;code class=&quot;language-text&quot;&gt;/allowed&lt;/code&gt;)를 반드시 설정하세요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비용&lt;/strong&gt;: COKACDIR 자체는 무료지만, 연결하는 코딩 에이전트(Claude Code 등)의 사용 요금은 별도입니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컴퓨터 상태&lt;/strong&gt;: 내 컴퓨터가 켜져 있고 인터넷에 연결되어 있어야 합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MIT 라이선스&lt;/strong&gt;: 소프트웨어는 &quot;AS IS&quot;로 제공되며, 데이터 손실 등에 대한 책임은 사용자에게 있습니다&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&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;&lt;strong&gt;한 줄 요약&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;텔레그램으로 Claude Code/Codex/Gemini를 원격 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;추천 대상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이동 중에도 AI 코딩을 사용하고 싶은 개발자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;비용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;COKACDIR 무료, 에이전트 비용 별도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;설치 시간&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;약 5분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/kstost/cokacdir&quot;&gt;kstost/cokacdir&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&quot;AI 코딩 에이전트는 좋은데 항상 컴퓨터 앞에 있어야 한다&quot;는 불편함을 정확히 해결하는 도구입니다. 특히 Claude Code를 주력으로 쓰는 개발자라면, 텔레그램 봇 하나 만들어두는 것만으로 활용도가 크게 올라갑니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Claude Code가 설치되어 있어야 하나요?&lt;/h3&gt;
&lt;p&gt;네. COKACDIR은 새로운 AI가 아니라, 기존에 설치된 코딩 에이전트의 리모컨 역할입니다. Claude Code, Codex CLI, Gemini CLI 중 하나 이상이 설치되어 있어야 합니다.&lt;/p&gt;
&lt;h3&gt;Q. 컴퓨터를 끄면 어떻게 되나요?&lt;/h3&gt;
&lt;p&gt;COKACDIR 서버가 꺼지므로 텔레그램에서 제어할 수 없습니다. 항상 접근하려면 클라우드 서버(AWS, GCP 등)에 설치하는 것을 추천합니다.&lt;/p&gt;
&lt;h3&gt;Q. 보안은 안전한가요?&lt;/h3&gt;
&lt;p&gt;텔레그램 봇 토큰과 &lt;code class=&quot;language-text&quot;&gt;/allowed&lt;/code&gt; 명령으로 접근을 제한할 수 있습니다. 다만 내 컴퓨터에서 AI가 직접 명령을 실행하므로, 봇 토큰 유출에 주의해야 합니다. AES-256 파일 암호화도 지원합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 AI 코딩 에이전트를 어떻게 원격으로 활용하고 계신가요? 다른 방법을 쓰고 계시다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Claude Code 입문 가이드 2026 — 설치부터 첫 프로젝트까지 완전 정리]]></title><description><![CDATA[Claude Code 입문 가이드 2026 커버 — 설치부터 첫 프로젝트 실행까지 완전 정리 안녕하세요, 자바파커입니다. "AI 코딩 도구가 너무 많은데, Claude Code…]]></description><link>https://blog.javapark.kr/beginner-guide-2026/</link><guid isPermaLink="false">https://blog.javapark.kr/beginner-guide-2026/</guid><pubDate>Wed, 01 Apr 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-beginner-guide-2026.png&quot; alt=&quot;Claude Code 입문 가이드 2026 커버 — 설치부터 첫 프로젝트 실행까지 완전 정리&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;AI 코딩 도구가 너무 많은데, Claude Code는 뭐가 다른 건가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 말하면 — &lt;strong&gt;직접 써보기 전에는 저도 반신반의했습니다.&lt;/strong&gt; 터미널에서 AI가 코드를 짠다고? 결국 ChatGPT에 코드 달라고 하는 거랑 뭐가 다르지?&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;완전히 다릅니다.&lt;/strong&gt; Claude Code는 대화창에서 코드를 복붙하는 게 아니라, 내 프로젝트 폴더 안에서 직접 파일을 읽고, 수정하고, 테스트까지 실행하는 AI 에이전트입니다. 오늘은 설치부터 첫 프로젝트까지 한 번에 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code란? — 30초 정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Claude Code는 Anthropic이 만든 AI 코딩 에이전트&lt;/strong&gt;입니다. 터미널(또는 IDE)에서 자연어로 지시하면, AI가 프로젝트 전체를 이해하고 직접 코드를 작성·수정·실행합니다.&lt;/p&gt;
&lt;p&gt;기존 AI 코딩 도구와의 차이를 정리하면:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ChatGPT/웹 AI&lt;/th&gt;
&lt;th&gt;GitHub Copilot&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;작동 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대화창에서 코드 생성&lt;/td&gt;
&lt;td&gt;에디터에서 자동완성&lt;/td&gt;
&lt;td&gt;프로젝트 전체를 이해하고 직접 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;파일 접근&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;불가 (복붙 필요)&lt;/td&gt;
&lt;td&gt;현재 파일 중심&lt;/td&gt;
&lt;td&gt;프로젝트 전체 파일 읽기/쓰기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;명령 실행&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;터미널 명령 직접 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;컨텍스트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대화 내용만&lt;/td&gt;
&lt;td&gt;열린 파일 주변&lt;/td&gt;
&lt;td&gt;최대 1M 토큰 (프로젝트 전체)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자율성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;높음 (멀티스텝 작업)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;한 줄 요약: &lt;strong&gt;&quot;내 컴퓨터에서 일하는 AI 개발자&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치 방법 — 3분이면 끝&lt;/h2&gt;
&lt;h3&gt;시스템 요구사항&lt;/h3&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;&lt;strong&gt;OS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS 13+, Windows 10+, Ubuntu 20.04+, Debian 10+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4GB 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;필수&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Windows: Git for Windows 설치 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;설치 (권장: 네이티브 설치)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-fsSL&lt;/span&gt; https://claude.ai/install.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;irm&lt;/span&gt; https:&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;claude&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ai/install&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;iex&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Homebrew (macOS):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; claude-code&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설치 후 바로 실행:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;claude&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫 실행 시 Anthropic 계정 로그인 화면이 나옵니다. 브라우저가 열리면 로그인하고 권한을 승인하면 끝입니다.&lt;/p&gt;
&lt;h3&gt;IDE 연동 (선택)&lt;/h3&gt;
&lt;p&gt;CLI 외에 IDE에서도 사용할 수 있습니다.&lt;/p&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;&lt;strong&gt;VS Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;확장 마켓플레이스에서 &quot;Claude Code&quot; 검색 → 설치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JetBrains&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;플러그인 마켓플레이스에서 &quot;Claude Code&quot; 검색 → 설치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데스크톱 앱&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS / Windows 전용 앱 (claude.ai에서 다운로드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;웹&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;claude.ai/code 에서 바로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;개인적으로는 &lt;strong&gt;터미널(CLI)에서 시작하는 걸 추천&lt;/strong&gt;합니다. Claude Code의 핵심 경험이 터미널에 있고, IDE 확장은 결국 내부적으로 CLI를 호출하는 구조입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;요금제 — 얼마나 드나?&lt;/h2&gt;
&lt;p&gt;Claude Code는 &lt;strong&gt;무료 플랜이 없습니다.&lt;/strong&gt; 아래 중 하나가 필요합니다.&lt;/p&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;Claude Code 사용량&lt;/th&gt;
&lt;th&gt;추천 대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pro&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;가볍게 체험&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max 5x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;넉넉함&lt;/td&gt;
&lt;td&gt;개인 개발자 (추천)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max 20x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;td&gt;거의 무제한&lt;/td&gt;
&lt;td&gt;헤비 유저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$30/인&lt;/td&gt;
&lt;td&gt;팀 공유&lt;/td&gt;
&lt;td&gt;소규모 팀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;별도 문의&lt;/td&gt;
&lt;td&gt;커스텀&lt;/td&gt;
&lt;td&gt;대규모 조직&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;실제 비용 감각:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일반적인 개발자 기준 &lt;strong&gt;하루 $6~12&lt;/strong&gt; 정도 사용&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/cost&lt;/code&gt; 명령으로 현재 세션의 토큰 사용량과 비용을 실시간 확인 가능&lt;/li&gt;
&lt;li&gt;Sonnet 모델을 기본으로 쓰면 비용을 크게 줄일 수 있고, 복잡한 작업에만 Opus로 전환하는 전략이 효과적&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;처음 시작한다면 &lt;strong&gt;Pro($20)로 체험 → Max($100)로 전환&lt;/strong&gt;하는 경로를 추천합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;핵심 기능 5가지 — 이것만 알면 시작 가능&lt;/h2&gt;
&lt;h3&gt;1. 자연어로 코딩&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 로그인 API 엔드포인트를 만들어줘. Express.js 기반으로,
  JWT 토큰 발급까지 포함해서.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code가 필요한 파일을 만들고, 패키지를 설치하고, 코드를 작성합니다. 사람은 결과를 확인하고 승인하면 됩니다.&lt;/p&gt;
&lt;h3&gt;2. 프로젝트 전체 이해&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 이 프로젝트의 인증 흐름을 설명해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로젝트 전체 파일을 읽고 구조를 분석합니다. 최대 1M 토큰(약 300만 자)의 컨텍스트 윈도우 덕분에 대규모 프로젝트도 한 번에 이해합니다.&lt;/p&gt;
&lt;h3&gt;3. Git 통합&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 지금까지 변경사항을 커밋해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;git add&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;commit&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;push&lt;/code&gt;, PR 생성까지 자연어로 처리합니다. 커밋 메시지도 변경 내용을 분석해서 자동 생성합니다.&lt;/p&gt;
&lt;h3&gt;4. CLAUDE.md — AI에게 주는 프로젝트 매뉴얼&lt;/h3&gt;
&lt;p&gt;프로젝트 루트에 &lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 파일을 만들면, Claude Code가 매 세션마다 이 파일을 읽고 프로젝트의 규칙과 컨벤션을 따릅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; CLAUDE.md&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;##&lt;/span&gt; 프로젝트 규칙&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; TypeScript strict 모드 사용
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 함수명은 camelCase
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 테스트는 Vitest로 작성
&lt;span class=&quot;token list punctuation&quot;&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;5. MCP 서버 — 외부 도구 연결&lt;/h3&gt;
&lt;p&gt;MCP(Model Context Protocol)를 통해 Claude Code에 외부 도구를 연결할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스&lt;/strong&gt;: Supabase, PostgreSQL 직접 쿼리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;브라우저&lt;/strong&gt;: Playwright로 웹 자동화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API&lt;/strong&gt;: Slack, GitHub, Jira 등 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 예: Playwright MCP 서버 연결&lt;/span&gt;
claude mcp &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; playwright npx @anthropic-ai/mcp-playwright&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;연결하면 Claude Code가 브라우저를 직접 조작하거나, DB를 조회하거나, Slack에 메시지를 보낼 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;첫 프로젝트 실습 — React Todo 앱 만들기&lt;/h2&gt;
&lt;p&gt;실제로 Claude Code를 사용해서 간단한 프로젝트를 만들어보겠습니다.&lt;/p&gt;
&lt;h3&gt;Step 1: 프로젝트 시작&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; my-todo-app
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-todo-app
claude&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Step 2: 프로젝트 생성 요청&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; React + TypeScript로 Todo 앱을 만들어줘.
  Vite 기반으로 세팅하고, 할 일 추가/완료/삭제 기능을 포함해줘.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code가 하는 일:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;npm create vite&lt;/code&gt; 실행&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;h3&gt;Step 3: 기능 추가&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 할 일 목록을 localStorage에 저장해서 새로고침해도 유지되게 해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Step 4: 확인 및 실행&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; dev 서버 실행해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 과정이 &lt;strong&gt;5~10분&lt;/strong&gt; 안에 끝납니다. 물론 프로덕션 품질의 코드는 아니지만, 프로토타이핑이나 학습 목적으로는 압도적으로 빠릅니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;알아두면 좋은 명령어&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;도움말&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/cost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 세션 비용 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨텍스트 정리 (긴 세션에서 유용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;프로젝트 메모리 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;코드 리뷰 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Shift+Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모델 전환 (Opus ↔ Sonnet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 작업 중단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;실전 팁 5가지&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. CLAUDE.md를 먼저 만들어라&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로젝트 시작 전에 규칙을 정해두면 일관된 코드를 받을 수 있습니다. 없이 시작하면 매번 &quot;TypeScript로 해줘&quot;, &quot;테스트도 작성해줘&quot;를 반복하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 작은 단위로 요청하라&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&quot;전체 쇼핑몰 만들어줘&quot; 보다 &quot;상품 목록 API 만들어줘&quot; → &quot;장바구니 기능 추가해줘&quot; 식으로 나눠서 요청하는 게 결과물 품질이 높습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. /compact를 적극 활용하라&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;긴 세션에서 컨텍스트가 쌓이면 속도가 느려지고 비용이 올라갑니다. 한 작업이 끝나면 &lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;로 정리하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Sonnet을 기본으로, Opus는 필요할 때만&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일상적인 코딩은 Sonnet으로 충분합니다. 복잡한 아키텍처 설계나 대규모 리팩토링에만 Opus를 사용하면 비용을 절반 이하로 줄일 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;Shift+Tab&lt;/code&gt;으로 전환합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Git 커밋을 자주 하라&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code가 코드를 수정하다가 잘못된 방향으로 갈 수 있습니다. 중간중간 커밋해두면 언제든 되돌릴 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;다음 단계&lt;/h2&gt;
&lt;p&gt;이 글에서 다룬 건 기본 중의 기본입니다. Claude Code를 더 깊이 활용하고 싶다면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP 서버 연결&lt;/strong&gt;: 데이터베이스, 브라우저, 외부 API를 Claude Code에 연결하는 방법&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook 활용&lt;/strong&gt;: 자동 린트, 테스트 실행, 커밋 메시지 검증 자동화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 스킬&lt;/strong&gt;: 반복 작업을 슬래시 명령어로 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 주제들은 후속 포스팅에서 다루겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q. Claude Code는 무료로 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;아니요. 최소 Pro 플랜($20/월)이 필요합니다. 무료 체험은 제공되지 않지만, Pro 플랜으로 가볍게 시작한 뒤 필요하면 Max로 업그레이드하는 방식을 추천합니다.&lt;/p&gt;
&lt;h3&gt;Q. Windows에서도 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;네. Windows 10 이상에서 네이티브로 지원됩니다. 단, Git for Windows가 사전 설치되어 있어야 합니다. PowerShell에서 &lt;code class=&quot;language-text&quot;&gt;irm https://claude.ai/install.ps1 | iex&lt;/code&gt; 명령으로 설치할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Q. 인터넷이 안 되면 사용할 수 없나요?&lt;/h3&gt;
&lt;p&gt;맞습니다. Claude Code는 Anthropic 서버와 통신해서 작동하므로 인터넷 연결이 필수입니다. 오프라인 모드는 지원하지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 AI 코딩 도구를 어떻게 활용하고 계신가요? Claude Code를 써보신 경험이 있다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AI 에이전트란? 2026년 현재 총정리 — 챗봇과 뭐가 다르고, 뭘 할 수 있나]]></title><description><![CDATA[AI 에이전트 2026 총정리 커버 — 챗봇·코파일럿과의 차이점과 주요 도구 안녕하세요, 자바파커입니다. "AI 에이전트가 대세라는데, 그게 ChatGPT…]]></description><link>https://blog.javapark.kr/ai-agent-guide-2026/</link><guid isPermaLink="false">https://blog.javapark.kr/ai-agent-guide-2026/</guid><pubDate>Wed, 01 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/ai-agent-guide-2026.png&quot; alt=&quot;AI 에이전트 2026 총정리 커버 — 챗봇·코파일럿과의 차이점과 주요 도구&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;AI 에이전트가 대세라는데, 그게 ChatGPT랑 뭐가 다른 건가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 말하면, 저도 처음엔 헷갈렸습니다. 챗봇, 코파일럿, 에이전트 — 다 AI 아닌가? 근데 직접 써보면 &lt;strong&gt;완전히 다른 경험&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;챗봇은 대화하고, 코파일럿은 도와주고, 에이전트는 직접 일합니다.&lt;/strong&gt; 2026년 현재 AI 에이전트 시장은 $7.6B 규모로, 챗봇 시장 성장률의 2배 속도로 커지고 있습니다. 오늘은 이 모든 것을 한 번에 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 에이전트란? — 30초 정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AI 에이전트(AI Agent)는 목표를 받으면, 스스로 계획을 세우고, 도구를 사용해서, 여러 단계에 걸쳐 작업을 실행하는 AI 시스템&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;비유하자면 이렇습니다:&lt;/p&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;th&gt;AI 에이전트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;비유&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;안내 데스크 직원&lt;/td&gt;
&lt;td&gt;옆에서 도와주는 동료&lt;/td&gt;
&lt;td&gt;업무를 위임받은 대리인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;하는 일&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;질문에 답변&lt;/td&gt;
&lt;td&gt;작업을 보조&lt;/td&gt;
&lt;td&gt;목표를 받아 자율적으로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자율성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;낮음 (반응형)&lt;/td&gt;
&lt;td&gt;중간 (제안형)&lt;/td&gt;
&lt;td&gt;높음 (실행형)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;작업 범위&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대화창 안&lt;/td&gt;
&lt;td&gt;특정 앱 안&lt;/td&gt;
&lt;td&gt;여러 시스템을 넘나듦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인간 개입&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매번 필요&lt;/td&gt;
&lt;td&gt;승인/거절&lt;/td&gt;
&lt;td&gt;최소한의 감독&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;한 줄로 요약하면:&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;hr&gt;
&lt;h2&gt;AI 에이전트 실전 예시 — 챗봇·코파일럿과 비교&lt;/h2&gt;
&lt;p&gt;같은 작업을 세 가지 방식으로 비교해보면 차이가 확 느껴집니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;매월 12개 부서 보고서를 합쳐서 경영진 요약 보고서를 만들어라&quot;&lt;/strong&gt;&lt;/p&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;&lt;strong&gt;챗봇&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&quot;보고서를 합치는 방법을 알려줘&quot; → 방법을 텍스트로 설명 → &lt;strong&gt;사람이 직접 실행&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;코파일럿&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;엑셀에서 작업 중 &quot;이 수식 추천해줘&quot; → 수식 제안 → &lt;strong&gt;사람이 수락하고 실행&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;에이전트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&quot;12개 파일 합쳐서 요약 보고서 만들어줘&quot; → &lt;strong&gt;파일 읽기 → 데이터 합치기 → 분석 → 보고서 생성까지 직접 수행&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;에이전트는 결과물까지 만들어냅니다.&lt;/strong&gt; 사람은 최종 확인만 하면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2026년 AI 에이전트 주요 플랫폼 — 한눈에 비교&lt;/h2&gt;
&lt;h3&gt;코딩 에이전트&lt;/h3&gt;
&lt;p&gt;개발자에게 가장 직접적인 AI 에이전트 카테고리입니다.&lt;/p&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;th&gt;작동 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;서브에이전트, Hook, 1M 컨텍스트, MCP&lt;/td&gt;
&lt;td&gt;터미널 / VS Code / 브라우저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codex CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;클라우드 샌드박스, 병렬 실행&lt;/td&gt;
&lt;td&gt;터미널&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini 3, 1M 컨텍스트, Extensions&lt;/td&gt;
&lt;td&gt;터미널&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cursor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anysphere&lt;/td&gt;
&lt;td&gt;$500M+ ARR, 병렬 에이전트 8개&lt;/td&gt;
&lt;td&gt;IDE (VS Code 포크)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Devin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cognition&lt;/td&gt;
&lt;td&gt;가장 자율적, 클라우드 IDE&lt;/td&gt;
&lt;td&gt;웹 / Slack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Agent Mode, VM 자동 실행, PR 생성&lt;/td&gt;
&lt;td&gt;IDE / 웹&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 블로그에서 다뤄온 &lt;strong&gt;Claude Code, Codex CLI, Gemini CLI&lt;/strong&gt;도 모두 AI 에이전트입니다. 터미널에서 자연어로 지시하면 파일을 읽고, 코드를 작성하고, 테스트를 실행하는 — 그 전체 과정이 &quot;에이전트&quot;입니다.&lt;/p&gt;
&lt;h3&gt;브라우저 에이전트&lt;/h3&gt;
&lt;p&gt;2025~2026년 가장 폭발적으로 성장한 카테고리입니다. AI가 &lt;strong&gt;직접 브라우저를 조작&lt;/strong&gt;합니다.&lt;/p&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;&lt;strong&gt;ChatGPT Agent (Operator)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;CUA 모델 기반, 스크린샷으로 화면을 &quot;보고&quot; 마우스/키보드로 &quot;조작&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project Mariner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Chrome 확장, 동시 10개 작업, WebVoyager 83.5% (SOTA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Atlas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;전용 AI 브라우저, Agent Mode로 다단계 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;예: &quot;항공편을 검색해서 가격 비교하고, 가장 저렴한 옵션을 정리해줘&quot;
→ AI가 브라우저를 열고, 검색 사이트를 탐색하고, 결과를 표로 정리&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;워크플로우/비즈니스 에이전트&lt;/h3&gt;
&lt;p&gt;개발 없이도 업무 프로세스를 자동화합니다.&lt;/p&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;&lt;strong&gt;Copilot Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;160,000+ 조직 배포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agentforce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Salesforce&lt;/td&gt;
&lt;td&gt;$540M ARR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이메일 발송, 문서 생성, CRM 업데이트, 회의 예약 등을 로우코드/노코드로 구축합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 에이전트를 연결하는 두 가지 표준 — MCP와 A2A&lt;/h2&gt;
&lt;p&gt;에이전트가 강력한 이유는 &lt;strong&gt;외부 도구와 연결&lt;/strong&gt;할 수 있기 때문입니다. 2026년 현재 두 가지 표준이 자리잡았습니다.&lt;/p&gt;
&lt;h3&gt;MCP (Model Context Protocol) — AI와 도구를 연결&lt;/h3&gt;
&lt;p&gt;Anthropic이 만들고, 현재 Linux Foundation 산하로 이관된 오픈소스 표준입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;AI 모델 ←→ MCP ←→ 외부 도구 (Slack, Google Drive, Jira, DB 등)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;2024년 11월 출시 → 2026년 3월 현재 &lt;strong&gt;월간 9,700만 다운로드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;5,800+ MCP 서버, 300+ MCP 클라이언트&lt;/li&gt;
&lt;li&gt;OpenAI, Google, Microsoft 모두 채택&lt;/li&gt;
&lt;li&gt;AI 세계의 &lt;strong&gt;&quot;USB-C&quot;&lt;/strong&gt; — 어떤 모델이든, 어떤 도구든 하나의 표준으로 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;A2A (Agent-to-Agent) — AI 에이전트 간 통신&lt;/h3&gt;
&lt;p&gt;Google이 만든 오픈 프로토콜로, &lt;strong&gt;에이전트끼리 대화하고 협업하는 표준&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;영업 에이전트 ←→ A2A ←→ 재무 에이전트 ←→ A2A ←→ HR 에이전트&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;150개 이상 조직 참여 (Atlassian, PayPal, Salesforce, SAP 등)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Card&lt;/strong&gt;: JSON 형태로 &quot;나는 이런 일을 할 수 있어&quot;를 광고&lt;/li&gt;
&lt;li&gt;다른 에이전트가 Agent Card를 보고 최적의 협업 상대를 찾아 통신&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MCP vs A2A 차이:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP&lt;/strong&gt; = AI ↔ 도구 (수직적 연결)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A2A&lt;/strong&gt; = AI ↔ AI (수평적 연결)&lt;/li&gt;
&lt;li&gt;둘은 경쟁이 아니라 &lt;strong&gt;상호 보완&lt;/strong&gt; 관계&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2026년 AI 에이전트 핵심 트렌드 5가지&lt;/h2&gt;
&lt;h3&gt;1. 멀티 에이전트 시스템의 부상&lt;/h3&gt;
&lt;p&gt;혼자 일하는 에이전트에서 &lt;strong&gt;팀으로 일하는 에이전트&lt;/strong&gt;로 전환되고 있습니다.&lt;/p&gt;
&lt;p&gt;Gartner에 따르면 멀티 에이전트 시스템 관련 문의가 &lt;strong&gt;1,445% 급증&lt;/strong&gt;했습니다. &quot;퍼핏티어(Puppeteer)&quot; 역할의 오케스트레이터가 전문 에이전트들을 조율하는 구조입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;오케스트레이터 에이전트
  ├── 코드 작성 에이전트
  ├── 코드 리뷰 에이전트
  ├── 테스트 에이전트
  └── 배포 에이전트&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. 장기 실행 자율 워크플로우&lt;/h3&gt;
&lt;p&gt;단일 프롬프트 → 단일 응답이 아니라, &lt;strong&gt;며칠간 실행되는 작업 루프&lt;/strong&gt;가 등장했습니다.&lt;/p&gt;
&lt;p&gt;에이전트가 전체 애플리케이션을 구축하고, 인간은 전략적 결정 포인트에서만 개입합니다. Claude Code의 백그라운드 태스크, Devin의 클라우드 실행이 대표적입니다.&lt;/p&gt;
&lt;h3&gt;3. 비개발자의 에이전트 시대&lt;/h3&gt;
&lt;p&gt;Microsoft Copilot Studio, Salesforce Agentforce처럼 &lt;strong&gt;코드 없이 에이전트를 만드는 플랫폼&lt;/strong&gt;이 본격화됐습니다. &quot;비즈니스 사용자가 엔지니어가 아닌데도 에이전트를 만들고 있다&quot;는 게 2026년의 현실입니다.&lt;/p&gt;
&lt;h3&gt;4. 에이전트 프레임워크 통합&lt;/h3&gt;
&lt;p&gt;각 빅테크가 에이전트 프레임워크를 출시했습니다:&lt;/p&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;Anthropic&lt;/td&gt;
&lt;td&gt;Claude Agent SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Agents SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Agent Development Kit (ADK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Semantic Kernel + AutoGen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;커뮤니티에서도 LangGraph, CrewAI, Mastra 등이 경쟁 중입니다.&lt;/p&gt;
&lt;h3&gt;5. MCP가 인프라 표준으로 확장&lt;/h3&gt;
&lt;p&gt;2025년이 &quot;채택의 해&quot;였다면, 2026년은 &quot;확장의 해&quot;입니다. Anthropic, OpenAI, Block이 공동 설립한 **Agentic AI Foundation(AAIF)**이 Linux Foundation 산하에서 MCP를 관리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 에이전트의 위험과 한계 — 솔직한 정리&lt;/h2&gt;
&lt;p&gt;AI 에이전트가 만능은 아닙니다. 알고 쓰는 게 중요합니다.&lt;/p&gt;
&lt;h3&gt;할루시네이션 — &quot;자신감 있게 틀리는&quot; 문제&lt;/h3&gt;
&lt;p&gt;에이전트는 &lt;strong&gt;자신의 능력 한계를 모릅니다.&lt;/strong&gt; 전문 지식이 필요한 상황인데도 자신 있게 답을 내놓을 수 있습니다. 고위험 작업에서는 반드시 사람의 검증이 필요합니다.&lt;/p&gt;
&lt;h3&gt;보안 — 과도한 권한 문제&lt;/h3&gt;
&lt;p&gt;보고에 따르면 &lt;strong&gt;90%의 에이전트가 필요한 것보다 10배 많은 권한&lt;/strong&gt;을 가지고 있습니다. 도구 연결이 늘어날수록 위험이 곱셈으로 증가합니다. 간접 프롬프트 인젝션(웹페이지에 숨겨진 악의적 지시)도 실제 위협입니다.&lt;/p&gt;
&lt;h3&gt;비용 — 과소평가되는 현실&lt;/h3&gt;
&lt;p&gt;Gartner에 따르면 CIO들이 AI 비용을 &lt;strong&gt;최대 1,000%까지 과소평가&lt;/strong&gt;한다고 합니다. 특히 에이전트를 확장하고 유지하는 비용이 예상보다 큽니다.&lt;/p&gt;
&lt;h3&gt;실패율 — 40% 이상 실패 예상&lt;/h3&gt;
&lt;p&gt;에이전트 프로젝트의 &lt;strong&gt;40% 이상이 2027년까지 실패&lt;/strong&gt;할 것이라는 Gartner 예측이 있습니다. 기술이 아니라 &lt;strong&gt;거버넌스&lt;/strong&gt;(통제, 소유권, 신뢰 관리)가 가장 큰 장벽입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;비개발자용 AI 에이전트 추천 — 지금 바로 시작하기&lt;/h2&gt;
&lt;p&gt;&quot;에이전트가 뭔지는 알겠는데, 나는 뭘 쓰면 되나요?&quot;&lt;/p&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;파일/데이터 정리&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;$20 (Pro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 작업 자동화&lt;/td&gt;
&lt;td&gt;ChatGPT Agent (Operator)&lt;/td&gt;
&lt;td&gt;$20 (Plus)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;업무 프로세스 자동화&lt;/td&gt;
&lt;td&gt;Copilot Studio&lt;/td&gt;
&lt;td&gt;M365 구독&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코딩&lt;/td&gt;
&lt;td&gt;Claude Code, Cursor&lt;/td&gt;
&lt;td&gt;$20~&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;가장 진입 장벽이 낮은 건 &lt;strong&gt;Claude Code&lt;/strong&gt;입니다. 터미널에서 자연어로 지시하면 파일을 직접 읽고, 수정하고, 생성합니다. &lt;a href=&quot;/claude-code-codex-for-non-developers/&quot;&gt;비개발자도 쓸 수 있다는 걸 이전 포스팅에서 다뤘습니다.&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;AI 에이전트가 내 일자리를 대체하나요?&lt;/h3&gt;
&lt;p&gt;단기적으로는 &quot;대체&quot;보다 &quot;증강&quot;에 가깝습니다. 에이전트는 반복적이고 규칙적인 작업을 처리하고, 사람은 판단·창의·소통에 집중하는 구조입니다. 다만 장기적으로 자동화 가능한 업무 범위가 넓어지는 건 사실입니다.&lt;/p&gt;
&lt;h3&gt;MCP를 개인이 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;네. Claude Code에서 MCP 서버를 설정하면 Google Drive, Slack, GitHub 등을 연결해서 사용할 수 있습니다. 설정법은 공식 문서에 잘 정리되어 있습니다.&lt;/p&gt;
&lt;h3&gt;어떤 에이전트 프레임워크를 배워야 하나요?&lt;/h3&gt;
&lt;p&gt;개발자라면 &lt;strong&gt;자신이 사용하는 AI 모델의 공식 SDK&lt;/strong&gt;부터 시작하세요. Claude 사용자라면 Claude Agent SDK, OpenAI 사용자라면 Agents SDK가 자연스럽습니다. 특정 벤더에 묶이고 싶지 않다면 LangGraph가 가장 범용적입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;AI 에이전트는 &lt;strong&gt;&quot;대화하는 AI&quot;에서 &quot;일하는 AI&quot;로의 전환&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;2026년 현재:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;코딩 에이전트&lt;/strong&gt; (Claude Code, Cursor, Devin)는 이미 실전 투입 수준&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;브라우저 에이전트&lt;/strong&gt; (Operator, Project Mariner)는 웹 작업을 자동화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP와 A2A&lt;/strong&gt;로 에이전트끼리 도구를 공유하고 협업하는 시대&lt;/li&gt;
&lt;li&gt;하지만 &lt;strong&gt;보안, 비용, 거버넌스&lt;/strong&gt; 문제는 아직 풀어야 할 숙제&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;에이전트를 &quot;써봐야 하나?&quot;고 고민 중이라면, 답은 간단합니다 — &lt;strong&gt;이미 쓰고 있을 가능성이 높습니다.&lt;/strong&gt; GitHub Copilot의 자동 완성, ChatGPT의 검색 기능, Claude Code의 파일 편집 — 이 모든 게 에이전트의 일부입니다.&lt;/p&gt;
&lt;p&gt;여러분은 어떤 AI 에이전트를 써보셨나요? 댓글로 경험을 공유해주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;관련 포스팅:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/claude-code-codex-for-non-developers/&quot;&gt;비개발자가 Claude Code, Codex CLI 활용하는 법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/ai-coding-skills-comparison/&quot;&gt;AI 코딩 도구 Skill 비교 — Claude Code vs Gemini CLI vs Codex CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;참고 자료:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code&quot;&gt;Claude Code 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io&quot;&gt;MCP 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/&quot;&gt;A2A 프로토콜 — Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gartner.com/en/articles/intelligent-agent-in-ai&quot;&gt;Gartner AI Agent Trends 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[비개발자가 Claude Code, Codex CLI 활용하는 법 — 코딩 몰라도 AI 에이전트 쓰는 시대]]></title><description><![CDATA[비개발자를 위한 Claude Code·Codex CLI 활용 가이드 커버 — AI 에이전트 시대의 노코딩 자동화 안녕하세요, 자바파커입니다. "Claude Code? Codex…]]></description><link>https://blog.javapark.kr/codex-for-non-developers/</link><guid isPermaLink="false">https://blog.javapark.kr/codex-for-non-developers/</guid><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-codex-for-non-developers.png&quot; alt=&quot;비개발자를 위한 Claude Code·Codex CLI 활용 가이드 커버 — AI 에이전트 시대의 노코딩 자동화&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Claude Code? Codex? 그거 개발자만 쓰는 거 아닌가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;솔직히 저도 처음엔 그렇게 생각했습니다. 터미널에 명령어를 치는 도구라니, 당연히 개발자 전용이겠거니 했죠. 그런데 직접 써보니 &lt;strong&gt;코딩을 전혀 모르는 사람이야말로 이 도구들의 진짜 수혜자&lt;/strong&gt;라는 걸 깨달았습니다.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;비개발자도 충분히 쓸 수 있고, 오히려 반복 업무 자동화에서 엄청난 효과를 볼 수 있습니다.&lt;/strong&gt; 오늘은 Claude Code와 OpenAI Codex CLI, 이 두 도구를 비개발자 관점에서 설치부터 실전 활용까지 정리해보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;먼저, 이 도구들이 뭔가요?&lt;/h2&gt;
&lt;p&gt;쉽게 비유하자면 이렇습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ChatGPT / Claude 웹&lt;/th&gt;
&lt;th&gt;Claude Code / Codex CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;비유&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;카카오톡으로 비서에게 부탁&lt;/td&gt;
&lt;td&gt;비서가 직접 내 컴퓨터 앞에 앉아서 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;할 수 있는 것&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;텍스트 답변, 조언&lt;/td&gt;
&lt;td&gt;파일 읽기/수정, 폴더 정리, 코드 실행, 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;작업 범위&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대화창 안에서만&lt;/td&gt;
&lt;td&gt;내 컴퓨터의 실제 파일과 폴더&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;는 Anthropic이 만든 AI 에이전트 도구이고, &lt;strong&gt;Codex CLI&lt;/strong&gt;는 OpenAI가 만든 도구입니다. 둘 다 터미널(명령 프롬프트)에서 자연어로 지시하면 &lt;strong&gt;내 컴퓨터의 파일을 직접 읽고, 수정하고, 생성&lt;/strong&gt;해줍니다.&lt;/p&gt;
&lt;p&gt;핵심은 이겁니다 — &lt;strong&gt;코딩을 해달라고 시키는 게 아니라, 일을 해달라고 시키는 겁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;비개발자가 할 수 있는 일 10가지&lt;/h2&gt;
&lt;p&gt;코딩과 전혀 관련 없는 실전 활용 사례입니다.&lt;/p&gt;
&lt;h3&gt;업무 자동화&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;경비 보고서 정리&lt;/strong&gt; — 여러 신용카드 명세서(CSV)를 하나로 합치고 카테고리별로 분류&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;/ol&gt;
&lt;h3&gt;콘텐츠 &amp;#x26; 마케팅&lt;/h3&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;블로그 포스팅 초안&lt;/strong&gt; — 주제를 주면 리서치 + 마크다운 초안까지 한 번에&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SNS 콘텐츠 배치 생성&lt;/strong&gt; — 하나의 긴 글에서 트위터/인스타/링크드인용 요약본 자동 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO 분석&lt;/strong&gt; — 경쟁사 페이지를 분석하고 키워드 리포트 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;데이터 분석&lt;/h3&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;&lt;strong&gt;스프레드시트 가공&lt;/strong&gt; — CSV/엑셀 파일에서 특정 조건의 데이터만 추출, 피벗&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;/ol&gt;
&lt;h3&gt;일상&lt;/h3&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;&lt;strong&gt;여행 계획&lt;/strong&gt; — 날짜, 예산, 취향을 알려주면 일정표 마크다운 파일로 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code 설치 가이드 (비개발자용)&lt;/h2&gt;
&lt;h3&gt;사전 준비&lt;/h3&gt;
&lt;p&gt;Claude Code를 쓰려면 두 가지가 필요합니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Anthropic 유료 구독&lt;/strong&gt; — 최소 Pro 플랜($20/월)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt; — Claude Code 설치에 필요한 기본 도구&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1단계: Node.js 설치&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org&quot;&gt;nodejs.org&lt;/a&gt; 접속 → &lt;strong&gt;LTS 버전&lt;/strong&gt; 다운로드 → 설치 (모든 옵션 기본값 OK)&lt;/p&gt;
&lt;p&gt;설치 확인:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# v22.x.x 같은 버전이 나오면 성공&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: Claude Code 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; @anthropic-ai/claude-code&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Windows 사용자&lt;/strong&gt;: PowerShell 또는 Git Bash에서 실행하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3단계: 실행 및 인증&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 작업할 폴더로 이동&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ~/Documents/내작업폴더

&lt;span class=&quot;token comment&quot;&gt;# Claude Code 실행&lt;/span&gt;
claude&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;처음 실행하면 브라우저가 열리면서 Anthropic 계정 인증을 요청합니다. 로그인하면 바로 사용 가능합니다.&lt;/p&gt;
&lt;h3&gt;4단계: 첫 번째 대화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 이 폴더에 있는 파일 목록을 정리해서 summary.md로 만들어줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이게 끝입니다. 코드를 작성한 게 아니라 &lt;strong&gt;한국어로 부탁한 것&lt;/strong&gt;뿐입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Codex CLI 설치 가이드 (비개발자용)&lt;/h2&gt;
&lt;h3&gt;사전 준비&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT Plus 이상 구독&lt;/strong&gt; — Plus($20/월) 또는 Pro($200/월)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js 18 이상&lt;/strong&gt; (위와 동일)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1단계: 설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; @openai/codex&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2단계: 인증&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;codex&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실행하면 &quot;Sign in with ChatGPT&quot; 옵션이 나옵니다. 브라우저에서 로그인하면 완료.&lt;/p&gt;
&lt;h3&gt;3단계: 첫 번째 대화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 이 폴더의 CSV 파일들을 읽고 날짜별로 정렬해서 하나로 합쳐줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Windows 사용자 주의&lt;/strong&gt;: Codex CLI의 Windows 지원은 아직 실험적입니다. WSL2(Windows Subsystem for Linux) 환경을 권장합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code vs Codex CLI — 비개발자 관점 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Codex CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;시작 난이도&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐ 쉬움&lt;/td&gt;
&lt;td&gt;⭐⭐ 쉬움 (Windows는 ⭐⭐⭐)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;한국어 지원&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매우 자연스러움&lt;/td&gt;
&lt;td&gt;자연스러움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;안전성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;변경 전 확인 요청 (감독형)&lt;/td&gt;
&lt;td&gt;full-auto 모드 있음 (비감독형 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows 지원&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;네이티브 지원&lt;/td&gt;
&lt;td&gt;실험적 (WSL 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;최소 비용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$20/월 (Pro)&lt;/td&gt;
&lt;td&gt;$20/월 (Plus)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;사용량 제한&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5시간 제한 (Pro)&lt;/td&gt;
&lt;td&gt;모델별 상이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데스크톱 앱&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;있음 (Mac/Windows)&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;웹 버전&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;있음 (claude.ai/code)&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VS Code 연동&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;비개발자에게 추천하는 선택은?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Claude Code를 먼저 추천합니다.&lt;/strong&gt; 이유는:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;감독형 방식&lt;/strong&gt; — 파일을 수정하기 전에 항상 확인을 요청합니다. 실수로 중요한 파일을 날릴 걱정이 적습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows 네이티브 지원&lt;/strong&gt; — WSL 설치 없이 바로 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데스크톱 앱/웹 버전&lt;/strong&gt; — 터미널이 어색하면 데스크톱 앱이나 웹에서 시작할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VS Code 확장&lt;/strong&gt; — VS Code에서 직접 사용하면 터미널을 따로 열 필요도 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Codex CLI는 &lt;strong&gt;이미 ChatGPT Plus를 구독 중&lt;/strong&gt;이거나, &lt;strong&gt;터미널 작업에 어느 정도 익숙한 분&lt;/strong&gt;에게 적합합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;비개발자를 위한 실전 팁 5가지&lt;/h2&gt;
&lt;h3&gt;1. CLAUDE.md (또는 AGENTS.md) 파일을 활용하세요&lt;/h3&gt;
&lt;p&gt;프로젝트 폴더에 &lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 파일을 만들어두면, Claude Code가 매번 대화를 시작할 때 이 파일을 읽습니다. 일종의 &lt;strong&gt;업무 매뉴얼&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;markdown&quot;&gt;&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; 이 폴더에 대한 설명&lt;/span&gt;

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 이 폴더에는 매월 경비 보고 CSV 파일이 저장됩니다
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 파일 형식: YYYY-MM-expenses.csv
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 카테고리: 교통, 식비, 숙박, 기타
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; 월말에 summary.xlsx로 합산 보고서를 만들어야 합니다&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 해두면 매번 같은 설명을 반복하지 않아도 됩니다.&lt;/p&gt;
&lt;h3&gt;2. 작업 전 백업 습관을 들이세요&lt;/h3&gt;
&lt;p&gt;AI가 파일을 직접 수정할 수 있으므로, 중요한 파일은 &lt;strong&gt;작업 전에 복사본&lt;/strong&gt;을 만들어두세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 작업 시작하기 전에 이 폴더를 backup 폴더로 복사해줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이것도 자연어로 시키면 됩니다.&lt;/p&gt;
&lt;h3&gt;3. 프롬프트는 구체적일수록 좋습니다&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;❌ &quot;이 파일 정리해줘&quot;
✅ &quot;expenses.csv에서 2024년 3월 데이터만 추출해서 march-report.csv로 저장해줘. 금액 기준 내림차순으로 정렬하고.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4. 모르겠으면 물어보세요&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 이 폴더에 어떤 파일들이 있는지 먼저 알려줘
&gt; 이 CSV 파일의 구조(컬럼)를 설명해줘
&gt; 이걸 엑셀로 변환할 수 있어?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code와 Codex 모두 &lt;strong&gt;대화형&lt;/strong&gt;이므로, 바로 작업을 시키지 않고 먼저 상황 파악을 요청해도 됩니다.&lt;/p&gt;
&lt;h3&gt;5. 비용 관리에 신경 쓰세요&lt;/h3&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;가끔 (주 1~2회)&lt;/td&gt;
&lt;td&gt;Pro / Plus&lt;/td&gt;
&lt;td&gt;~$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매일 가볍게&lt;/td&gt;
&lt;td&gt;Pro / Plus&lt;/td&gt;
&lt;td&gt;~$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매일 집중적으로&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;~$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;헤비 유저&lt;/td&gt;
&lt;td&gt;Max / Pro (Codex)&lt;/td&gt;
&lt;td&gt;$100~200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;특히 Claude Code Pro 플랜은 &lt;strong&gt;5시간 사용 제한&lt;/strong&gt;이 있으므로, 가벼운 작업 위주라면 충분하지만 하루 종일 붙잡고 있으면 금방 한도에 도달합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;비개발자가 자주 하는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Q. 터미널(명령 프롬프트)을 한 번도 써본 적이 없는데 괜찮을까요?&lt;/h3&gt;
&lt;p&gt;괜찮습니다. 필요한 명령어는 딱 세 개입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;cd 폴더경로&lt;/code&gt; — 폴더 이동&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;claude&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;codex&lt;/code&gt; — 도구 실행&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;exit&lt;/code&gt; — 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;나머지는 전부 자연어로 대화하면 됩니다. VS Code 확장이나 데스크톱 앱을 쓰면 터미널 자체를 건드릴 일이 더 줄어듭니다.&lt;/p&gt;
&lt;h3&gt;Q. AI가 중요한 파일을 잘못 수정하면 어떡하죠?&lt;/h3&gt;
&lt;p&gt;Claude Code는 &lt;strong&gt;기본적으로 파일을 수정하기 전에 확인을 요청&lt;/strong&gt;합니다. &quot;이 파일을 이렇게 바꿔도 될까요?&quot;라고 물어보고, 사용자가 승인해야 실행됩니다. Codex CLI도 기본 모드에서는 동일합니다. 다만 Codex의 &lt;code class=&quot;language-text&quot;&gt;full-auto&lt;/code&gt; 모드는 확인 없이 바로 실행하므로, 비개발자는 기본 모드를 유지하는 것을 권장합니다.&lt;/p&gt;
&lt;h3&gt;Q. ChatGPT나 Claude 웹에서 하던 작업과 뭐가 다른가요?&lt;/h3&gt;
&lt;p&gt;가장 큰 차이는 &lt;strong&gt;내 컴퓨터의 파일에 직접 접근&lt;/strong&gt;할 수 있다는 점입니다. 채팅 인터페이스에서는 &quot;이런 코드를 만들어줘&quot;라고 하면 텍스트로 결과를 보여주기만 합니다. Claude Code나 Codex는 실제로 파일을 만들고, 기존 파일을 수정하고, 프로그램을 실행까지 해줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;&quot;코딩을 모르면 AI 에이전트 도구를 못 쓴다&quot;는 건 이제 옛말입니다. Claude Code와 Codex CLI는 &lt;strong&gt;자연어로 지시하면 내 컴퓨터에서 직접 작업을 수행하는 도구&lt;/strong&gt;이고, 그 지시에 코드가 필요하지 않습니다.&lt;/p&gt;
&lt;p&gt;시작이 어렵게 느껴지시면, 딱 하나만 해보세요:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
claude
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 이 폴더에 있는 파일들을 정리해서 목록 만들어줘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 한 줄이 시작입니다. 한 번 경험하고 나면 &quot;이것도 시킬 수 있지 않을까?&quot; 하는 생각이 자연스럽게 따라올 겁니다.&lt;/p&gt;
&lt;p&gt;여러분은 AI 에이전트 도구를 어떤 업무에 활용하고 계신가요? 댓글로 공유해주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;관련 포스팅:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;./ai-coding-skills-comparison.md&quot;&gt;AI 코딩 도구 Skill 비교 — Claude Code vs Gemini CLI vs Codex CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;참고 문서:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code&quot;&gt;Claude Code 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex&quot;&gt;OpenAI Codex CLI 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[AI 코딩 도구 Skill 비교 — Claude Code vs Gemini CLI vs Codex CLI 커스텀 명령어 완전 정리]]></title><description><![CDATA[AI 코딩 도구 Skill 비교 커버 — Claude Code vs Gemini CLI vs Codex CLI 커스텀 명령어 안녕하세요, 자바파커입니다. "AI…]]></description><link>https://blog.javapark.kr/ai-coding-skills-comparison/</link><guid isPermaLink="false">https://blog.javapark.kr/ai-coding-skills-comparison/</guid><pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/ai-coding-skills-comparison.png&quot; alt=&quot;AI 코딩 도구 Skill 비교 커버 — Claude Code vs Gemini CLI vs Codex CLI 커스텀 명령어&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;AI 코딩 도구마다 커스텀 명령어 만드는 방식이 다르던데, 어떤 걸 써야 하죠?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;요즘 Claude Code, Gemini CLI, OpenAI Codex CLI 모두 &lt;strong&gt;반복 작업을 명령어 하나로 처리하는 기능&lt;/strong&gt;을 제공합니다. 이름은 조금씩 다르지만 핵심은 같습니다 — &lt;strong&gt;자주 하는 작업 지시를 파일로 저장해두고, 한 줄로 호출하는 것&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;결론부터 말씀드리면, 세 도구 모두 비슷한 목적을 달성하지만 &lt;strong&gt;설계 철학과 세부 구현이 꽤 다릅니다.&lt;/strong&gt; 직접 조사하고 비교해봤습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;한눈에 보는 3사 비교표&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Gemini CLI&lt;/th&gt;
&lt;th&gt;Codex CLI (OpenAI)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;커스텀 명령어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Skill (&lt;code class=&quot;language-text&quot;&gt;SKILL.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Custom Command (&lt;code class=&quot;language-text&quot;&gt;.toml&lt;/code&gt;) + Agent Skill (&lt;code class=&quot;language-text&quot;&gt;SKILL.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Skill (&lt;code class=&quot;language-text&quot;&gt;SKILL.md&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;프로젝트 설정 파일&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;GEMINI.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;호출 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/skill-name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/command-name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;$skill-name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자동 호출&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;description 기반&lt;/td&gt;
&lt;td&gt;Agent Skill만 자동&lt;/td&gt;
&lt;td&gt;description 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;파일 형식&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Markdown + YAML&lt;/td&gt;
&lt;td&gt;TOML (명령어) / Markdown (스킬)&lt;/td&gt;
&lt;td&gt;Markdown + YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;쉘 명령 주입&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;!`command`&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;!{command}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;서브에이전트 분리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스킬 배포/공유&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Git으로 공유&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;.skill&lt;/code&gt; 패키지, Extensions&lt;/td&gt;
&lt;td&gt;로컬 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;1. Claude Code — Skill&lt;/h2&gt;
&lt;p&gt;이전 포스팅에서 자세히 다뤘지만, 핵심만 짚겠습니다.&lt;/p&gt;
&lt;h3&gt;파일 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;~/.claude/skills/blog-post/
├── SKILL.md          # 필수: 프론트매터 + 지시문
├── reference.md      # 선택: 상세 문서
└── scripts/
    └── helper.py     # 선택: 헬퍼 스크립트&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;SKILL.md 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;post
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 블로그 포스팅 초안을 작성합니다
&lt;span class=&quot;token key atrule&quot;&gt;disable-model-invocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;allowed-tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Grep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Glob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WebSearch
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 블로그 포스팅 작성 스킬&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;주제($ARGUMENTS)를 받으면&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
1. 아웃라인을 먼저 잡는다
2. WebSearch로 최신 정보 확인
3. 마크다운 초안 작성&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;호출&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/blog-post Claude Code Skill 소개&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Claude Code만의 강점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt;&lt;/strong&gt; — 서브에이전트로 분리 실행해서 메인 대화가 오염되지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;allowed-tools&lt;/code&gt;&lt;/strong&gt; — 스킬별로 사용 가능한 도구를 제한 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;disable-model-invocation&lt;/code&gt;&lt;/strong&gt; — 배포 같은 위험한 작업은 수동 호출만 허용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;!`command`&lt;/code&gt;&lt;/strong&gt; — 쉘 명령어 결과를 동적으로 프롬프트에 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. Gemini CLI — Custom Command + Agent Skill&lt;/h2&gt;
&lt;p&gt;Gemini CLI는 커스텀 명령어 시스템을 &lt;strong&gt;두 가지로 분리&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;h3&gt;(1) Custom Command — 간단한 슬래시 명령어&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;TOML 파일&lt;/strong&gt;로 작성합니다. Claude Code나 Codex와 달리 마크다운이 아니라 TOML입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;~/.gemini/commands/commit.toml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;toml&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Git 커밋 메시지를 자동 생성합니다&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&quot;
다음 diff를 기반으로 Conventional Commit 메시지를 생성해주세요:

```diff
!&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;git diff --staged&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&quot;&quot;&quot;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
호출:
```bash
/commit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심 문법:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;!{command}&lt;/code&gt; — 쉘 명령어 실행 후 결과 주입&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;{{args}}&lt;/code&gt; — 사용자 인자 전달&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@{파일경로}&lt;/code&gt; — 파일/이미지/PDF 내용 주입 (멀티모달 지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하위 폴더로 네임스페이스도 지원합니다:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;.gemini/commands/git/commit.toml  →  /git:commit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;(2) Agent Skill — 자동 활성화되는 전문 지식&lt;/h3&gt;
&lt;p&gt;Custom Command와 별도로, &lt;strong&gt;모델이 자동으로 판단해서 활성화하는 스킬 시스템&lt;/strong&gt;이 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;.gemini/skills/code-reviewer/
├── SKILL.md
├── scripts/
└── references/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;reviewer
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 코드 리뷰를 수행합니다. 로컬 변경사항과 원격 PR 모두 지원합니다.
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Code Reviewer&lt;/span&gt;
이 스킬은 체계적인 코드 리뷰를 수행합니다.

&lt;span class=&quot;token comment&quot;&gt;## 워크플로우&lt;/span&gt;
1. 리뷰 대상 파악 (PR 번호 또는 로컬 변경사항)
2. 코드 분석
3. 리뷰 코멘트 생성&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;토큰 효율이 좋은 설계:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;세션 시작 시 이름+설명만 로드&lt;/li&gt;
&lt;li&gt;관련 작업이 감지되면 &lt;code class=&quot;language-text&quot;&gt;activate_skill&lt;/code&gt; 호출&lt;/li&gt;
&lt;li&gt;그때서야 전체 SKILL.md 본문이 컨텍스트에 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;관리 명령어:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/skills list
/skills disable code-reviewer
gemini skills &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; https://github.com/&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Gemini CLI만의 강점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TOML 기반 Command&lt;/strong&gt; — 간단한 명령어는 3줄이면 완성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;@{파일경로}&lt;/code&gt;&lt;/strong&gt; — 이미지, PDF 등 멀티모달 파일을 프롬프트에 바로 주입&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progressive Disclosure&lt;/strong&gt; — Agent Skill은 필요할 때만 전체 로드 (토큰 절약)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensions 시스템&lt;/strong&gt; — 스킬을 패키지로 묶어 설치/배포 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. Codex CLI (OpenAI) — Skill&lt;/h2&gt;
&lt;p&gt;Codex CLI의 Skill 시스템은 구조적으로 Claude Code와 가장 비슷합니다.&lt;/p&gt;
&lt;h3&gt;파일 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;.agents/skills/fix-issue/
├── SKILL.md          # 필수: 지시문
├── scripts/          # 선택: 실행 스크립트
├── references/       # 선택: 참고 문서
└── agents/
    └── openai.yaml   # 선택: UI 메타데이터&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;SKILL.md 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;issue
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; GitHub 이슈를 분석하고 코드를 수정합니다
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Fix Issue&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;이슈 번호를 받으면&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
1. 이슈 내용 분석
2. 관련 코드 탐색
3. 수정 구현
4. 테스트 작성&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;호출&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$fix&lt;/span&gt;-issue &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code의 &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;$&lt;/code&gt; 기호를 사용합니다.&lt;/p&gt;
&lt;h3&gt;스킬 저장 위치&lt;/h3&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;저장소 (CWD)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장소 (루트)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;$REPO_ROOT/.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;$HOME/.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/etc/codex/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;번들&lt;/td&gt;
&lt;td&gt;Codex 내장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Codex CLI만의 강점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;openai.yaml&lt;/code&gt; 메타데이터&lt;/strong&gt; — 아이콘, 브랜드 색상 등 UI 커스터마이징&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 의존성 선언&lt;/strong&gt; — 스킬에서 필요한 MCP 도구를 명시적으로 선언&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5단계 스코프&lt;/strong&gt; — CWD → 저장소 루트 → 사용자 → 관리자 → 시스템까지 세분화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;30+ 내장 슬래시 명령어&lt;/strong&gt; — &lt;code class=&quot;language-text&quot;&gt;/plan&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/diff&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/compact&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/review&lt;/code&gt; 등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;프로젝트 설정 파일 비교 — CLAUDE.md vs GEMINI.md vs AGENTS.md&lt;/h2&gt;
&lt;p&gt;스킬과 함께 알아두면 좋은 게 &lt;strong&gt;프로젝트 설정 파일&lt;/strong&gt;입니다. 세 도구 모두 &quot;프로젝트 전체에 적용되는 규칙&quot;을 마크다운 파일로 관리합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Gemini CLI&lt;/th&gt;
&lt;th&gt;Codex CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;파일명&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;GEMINI.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;위치&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;프로젝트 루트 + 하위 디렉토리&lt;/td&gt;
&lt;td&gt;프로젝트 루트 + 하위 디렉토리&lt;/td&gt;
&lt;td&gt;프로젝트 루트 + 하위 디렉토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;글로벌 설정&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;~/.gemini/GEMINI.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;~/.codex/AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;동적 발견&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;작업 디렉토리 기준&lt;/td&gt;
&lt;td&gt;JIT 로드 (파일 접근 시)&lt;/td&gt;
&lt;td&gt;CWD까지 누적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;크기 제한&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;32KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자동 생성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/init&lt;/code&gt; 명령어&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;/init&lt;/code&gt; 명령어&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;비유하자면, &lt;strong&gt;CLAUDE.md/GEMINI.md/AGENTS.md는 사무실 공지사항&lt;/strong&gt;, &lt;strong&gt;Skill은 업무 매뉴얼&lt;/strong&gt;입니다. 하나는 항상 읽히고, 하나는 필요할 때만 꺼냅니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;어떤 도구를 선택해야 할까?&lt;/h2&gt;
&lt;p&gt;솔직히 말하면, Skill 기능만으로 도구를 선택하진 않습니다. 하지만 각 도구의 Skill 설계에서 &lt;strong&gt;철학의 차이&lt;/strong&gt;가 보입니다.&lt;/p&gt;
&lt;h3&gt;Claude Code가 적합한 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;안전성 제어&lt;/strong&gt;가 중요할 때 — &lt;code class=&quot;language-text&quot;&gt;disable-model-invocation&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;allowed-tools&lt;/code&gt;로 세밀한 제어&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서브에이전트 분리&lt;/strong&gt;가 필요할 때 — &lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt;로 긴 작업을 깔끔하게 분리&lt;/li&gt;
&lt;li&gt;이미 Claude Code를 주력으로 사용 중일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gemini CLI가 적합한 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;간단한 명령어를 빠르게&lt;/strong&gt; 만들고 싶을 때 — TOML 3줄이면 충분&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;멀티모달 입력&lt;/strong&gt;이 필요할 때 — &lt;code class=&quot;language-text&quot;&gt;@{이미지.png}&lt;/code&gt; 같은 파일 주입&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;토큰 효율&lt;/strong&gt;을 중시할 때 — Progressive Disclosure 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Codex CLI가 적합한 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조직 단위 관리&lt;/strong&gt;가 필요할 때 — 5단계 스코프로 세분화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI 커스터마이징&lt;/strong&gt;이 필요할 때 — &lt;code class=&quot;language-text&quot;&gt;openai.yaml&lt;/code&gt;로 아이콘, 색상 설정&lt;/li&gt;
&lt;li&gt;이미 OpenAI 생태계를 사용 중일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;실전 팁 — 어떤 도구든 공통으로 적용되는 원칙&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. description을 잘 쓰세요.&lt;/strong&gt;
세 도구 모두 description을 보고 자동 호출 여부를 판단합니다. 모호하면 엉뚱한 타이밍에 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 부작용이 있는 작업은 수동 호출만 허용하세요.&lt;/strong&gt;
배포, 커밋, 메시지 전송 등은 실수로 자동 호출되면 큰일입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code: &lt;code class=&quot;language-text&quot;&gt;disable-model-invocation: true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gemini CLI: Custom Command는 기본이 수동&lt;/li&gt;
&lt;li&gt;Codex CLI: &lt;code class=&quot;language-text&quot;&gt;allow_implicit_invocation: false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 스킬 파일은 짧게 유지하세요.&lt;/strong&gt;
500줄 이내가 권장됩니다. 길어지면 별도 파일로 분리하고 참조하세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 팀과 공유하려면 프로젝트 디렉토리에 저장하세요.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Claude Code
.claude/skills/my-skill/SKILL.md

# Gemini CLI
.gemini/skills/my-skill/SKILL.md
.gemini/commands/my-command.toml

# Codex CLI
.agents/skills/my-skill/SKILL.md&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Git으로 커밋하면 팀원 모두 같은 명령어를 사용할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;SKILL.md 표준이 통일되어 있나요?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://agentskills.io&quot;&gt;Agent Skills&lt;/a&gt;라는 오픈 스탠다드가 있고, Gemini CLI와 Codex CLI가 이를 따르고 있습니다. Claude Code도 비슷한 구조를 사용하지만 프론트매터 옵션에 차이가 있습니다. 완전한 호환은 아니지만, 핵심 구조(YAML 프론트매터 + 마크다운 본문)는 동일합니다.&lt;/p&gt;
&lt;h3&gt;하나의 Skill 파일을 여러 도구에서 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;기본 구조가 비슷해서 &lt;strong&gt;간단한 스킬은 거의 그대로 사용 가능&lt;/strong&gt;합니다. 하지만 &lt;code class=&quot;language-text&quot;&gt;allowed-tools&lt;/code&gt;(Claude Code 전용), &lt;code class=&quot;language-text&quot;&gt;!{command}&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;!`command`&lt;/code&gt; 같은 도구별 고유 문법이 있어서 완전한 호환은 어렵습니다.&lt;/p&gt;
&lt;h3&gt;Skill과 프롬프트 복사-붙여넣기의 차이는 뭔가요?&lt;/h3&gt;
&lt;p&gt;프롬프트 복사-붙여넣기는 텍스트만 전달하는 거지만, Skill은 &lt;strong&gt;도구 사용 제한, 자동 호출 제어, 서브에이전트 분리, 쉘 명령어 동적 주입&lt;/strong&gt; 같은 제어가 가능합니다. 자동차와 자전거의 차이라고 보시면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구의 &lt;strong&gt;Skill/커스텀 명령어 기능은 이제 선택이 아니라 기본&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;세 도구 모두 &quot;반복 작업을 파일로 저장하고 한 줄로 호출한다&quot;는 핵심은 같지만:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;는 안전성 제어와 서브에이전트에 강하고&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt;는 간결함과 토큰 효율에 강하고&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codex CLI&lt;/strong&gt;는 조직 관리와 UI 커스터마이징에 강합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;어떤 도구를 쓰든, 반복하는 작업이 있다면 오늘 바로 Skill 파일 하나 만들어보세요.&lt;/p&gt;
&lt;p&gt;여러분은 어떤 AI 코딩 도구를 주로 사용하고 계신가요? 댓글로 알려주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/skills&quot;&gt;Claude Code 공식 문서 — Skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google-gemini/gemini-cli&quot;&gt;Gemini CLI GitHub 저장소&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex&quot;&gt;OpenAI Codex CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://agentskills.io&quot;&gt;Agent Skills 오픈 스탠다드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[블로그 글을 유튜브 영상으로 만드는 법 — NotebookLM 동영상 변환 가이드]]></title><description><![CDATA[블로그 글을 유튜브 영상으로 변환 커버 — NotebookLM AI…]]></description><link>https://blog.javapark.kr/blog-to-youtube-with-notebooklm/</link><guid isPermaLink="false">https://blog.javapark.kr/blog-to-youtube-with-notebooklm/</guid><pubDate>Sat, 28 Mar 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/blog-to-youtube-with-notebooklm.png&quot; alt=&quot;블로그 글을 유튜브 영상으로 변환 커버 — NotebookLM AI 동영상 변환 가이드&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;블로그에 글을 열심히 써도, 읽는 사람이 한정되어 있다는 걸 느낄 때가 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;이 글을 영상으로도 만들 수 있으면 좋겠는데, 촬영도 편집도 할 줄 모르는데...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Google NotebookLM을 쓰면 블로그 글 하나로 AI 동영상을 만들 수 있습니다.&lt;/strong&gt; 촬영 장비도, 영상 편집 기술도 필요 없습니다. 글만 있으면 됩니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 제가 실제로 블로그 포스팅을 NotebookLM 동영상으로 변환하고, 유튜브에 업로드한 전체 과정을 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;전체 워크플로우 — 3단계&lt;/h2&gt;
&lt;p&gt;전체 흐름을 먼저 보면 이렇습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;블로그 글 작성 → NotebookLM에 업로드 → AI 동영상 생성 → 유튜브 업로드&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;1. 글 작성&lt;/td&gt;
&lt;td&gt;티스토리 / 마크다운&lt;/td&gt;
&lt;td&gt;이미 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 동영상 생성&lt;/td&gt;
&lt;td&gt;NotebookLM&lt;/td&gt;
&lt;td&gt;5~30분 (자동)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 유튜브 업로드&lt;/td&gt;
&lt;td&gt;YouTube Studio&lt;/td&gt;
&lt;td&gt;5~10분&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심은 &lt;strong&gt;이미 쓴 글을 재활용&lt;/strong&gt;한다는 것입니다. 새로 콘텐츠를 만드는 게 아닙니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1단계: 블로그 글 준비&lt;/h2&gt;
&lt;h3&gt;어떤 글이 동영상으로 적합한가?&lt;/h3&gt;
&lt;p&gt;모든 글이 영상으로 잘 변환되는 건 아닙니다.&lt;/p&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;개념 설명, 비교 분석&lt;/td&gt;
&lt;td&gt;코드 위주의 튜토리얼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단계별 가이드&lt;/td&gt;
&lt;td&gt;짧은 팁/메모&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도구 소개, 후기&lt;/td&gt;
&lt;td&gt;코드 스니펫 모음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FAQ, 체크리스트&lt;/td&gt;
&lt;td&gt;표만 있는 정리글&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;예를 들어, &quot;AI로 웹서비스 만드는 법&quot; 같은 글은 동영상으로 만들면 시각적으로 설명이 풍부해져서 효과적입니다. 반면 코드 줄 하나하나를 설명하는 튜토리얼은 텍스트가 더 낫습니다.&lt;/p&gt;
&lt;h3&gt;글을 준비하는 방법&lt;/h3&gt;
&lt;p&gt;NotebookLM에 소스를 넣는 방법은 여러 가지입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;웹 URL 붙여넣기&lt;/strong&gt; — 티스토리 포스팅 URL을 그대로 입력&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;텍스트 복사/붙여넣기&lt;/strong&gt; — 마크다운이나 텍스트를 직접 붙여넣기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Docs&lt;/strong&gt; — 구글 문서로 옮긴 후 연결&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF 업로드&lt;/strong&gt; — 글을 PDF로 변환 후 업로드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가장 간단한 건 &lt;strong&gt;블로그 URL을 그대로 넣는 것&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2단계: NotebookLM으로 AI 동영상 생성&lt;/h2&gt;
&lt;h3&gt;NotebookLM 접속 및 노트북 생성&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://notebooklm.google.com&quot;&gt;NotebookLM&lt;/a&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; &gt; 웹사이트 URL 또는 텍스트 붙여넣기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[이미지: NotebookLM 소스 추가 화면]&lt;/p&gt;
&lt;h3&gt;Video Overview 생성&lt;/h3&gt;
&lt;p&gt;소스를 추가하면 오른쪽에 &lt;strong&gt;Studio 패널&lt;/strong&gt;이 나타납니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Studio 패널에서 &lt;strong&gt;Video Overview&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;li&gt;&lt;strong&gt;생성&lt;/strong&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[이미지: Video Overview 생성 버튼 클릭 화면]&lt;/p&gt;
&lt;h3&gt;동영상 형식 — 3가지 비교&lt;/h3&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;th&gt;추천 용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Brief&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1~2분, 핵심 요약&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;SNS 공유, 짧은 소개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explainer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3~5분, 개념 설명&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;유튜브 업로드, 학습 콘텐츠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cinematic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5분 이상, 풍부한 애니메이션&lt;/td&gt;
&lt;td&gt;Ultra ($250/월)&lt;/td&gt;
&lt;td&gt;프로 수준 프레젠테이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;무료 계정&lt;/strong&gt;으로는 Brief와 Explainer를 사용할 수 있습니다. 블로그 글을 유튜브 영상으로 만드는 용도라면 &lt;strong&gt;Explainer&lt;/strong&gt;가 가장 적합합니다.&lt;/p&gt;
&lt;h3&gt;커스텀 프롬프트 활용&lt;/h3&gt;
&lt;p&gt;그냥 생성해도 되지만, 프롬프트를 주면 결과물이 더 좋아집니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;이 글의 핵심 내용을 개발자 독자를 위해 설명해줘.
Claude Code와 Supabase의 조합이 왜 효과적인지에 초점을 맞춰줘.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프롬프트로 제어할 수 있는 것들:&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;h3&gt;생성 시간&lt;/h3&gt;
&lt;p&gt;생성 버튼을 누르면 5~30분 정도 걸립니다. 페이지를 닫아도 백그라운드에서 생성이 계속됩니다. 완료되면 노트북에서 확인할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3단계: 유튜브에 업로드&lt;/h2&gt;
&lt;h3&gt;동영상 다운로드&lt;/h3&gt;
&lt;p&gt;NotebookLM에서 생성된 동영상을 &lt;strong&gt;다운로드&lt;/strong&gt; 합니다. MP4 파일로 저장됩니다.&lt;/p&gt;
&lt;h3&gt;YouTube Studio에서 업로드&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://studio.youtube.com&quot;&gt;YouTube Studio&lt;/a&gt; 접속&lt;/li&gt;
&lt;li&gt;오른쪽 상단 &lt;strong&gt;만들기&lt;/strong&gt; &gt; &lt;strong&gt;동영상 업로드&lt;/strong&gt; 클릭&lt;/li&gt;
&lt;li&gt;다운로드한 MP4 파일 선택&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;메타데이터 설정 — SEO가 여기서도 중요&lt;/h3&gt;
&lt;p&gt;블로그 SEO와 마찬가지로, 유튜브도 검색 최적화가 중요합니다.&lt;/p&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;&lt;strong&gt;제목&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;블로그 제목을 그대로 쓰되, &quot;영상 ver.&quot; 느낌 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;블로그 원문 링크 + 핵심 내용 요약 3~5줄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;태그&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;블로그 태그와 동일 + &quot;NotebookLM&quot;, &quot;AI 동영상&quot; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;썸네일&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;블로그 대표이미지를 재활용하거나 약간 변형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;카테고리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;과학/기술 또는 교육&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;설명란 템플릿&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[블로그 원문] https://javapark.tistory.com/entry/글-슬러그

이 영상은 AI JAVAPARK 블로그 포스팅을 Google NotebookLM으로
AI 동영상으로 변환한 콘텐츠입니다.

## 목차
00:00 인트로
00:30 핵심 개념
02:00 실전 예시
04:00 정리

## 키워드
#AI개발 #ClaudeCode #NotebookLM

---
AI JAVAPARK — AI와 함께 만드는 개발 이야기
Blog: https://javapark.tistory.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;블로그 원문 링크를 설명란 맨 위에 넣으면 &lt;strong&gt;블로그 유입&lt;/strong&gt;도 늘어나고, 구글 SEO에서도 상호 연결 효과를 얻을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;콘텐츠 재활용 전략 — 글 하나로 3가지 만들기&lt;/h2&gt;
&lt;p&gt;이 워크플로우의 진짜 가치는 &lt;strong&gt;하나의 콘텐츠를 여러 형태로 재활용&lt;/strong&gt;하는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;블로그 글 (원본)
  ├── NotebookLM Explainer → 유튜브 영상
  ├── NotebookLM Brief → SNS 숏폼 (인스타, 쇼츠)
  └── NotebookLM Audio Overview → 팟캐스트&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;NotebookLM 형식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;블로그 글&lt;/td&gt;
&lt;td&gt;티스토리&lt;/td&gt;
&lt;td&gt;원본&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유튜브 영상&lt;/td&gt;
&lt;td&gt;YouTube&lt;/td&gt;
&lt;td&gt;Explainer / Cinematic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;숏폼 영상&lt;/td&gt;
&lt;td&gt;YouTube Shorts, 인스타&lt;/td&gt;
&lt;td&gt;Brief&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;팟캐스트/오디오&lt;/td&gt;
&lt;td&gt;Spotify, 팟빵&lt;/td&gt;
&lt;td&gt;Audio Overview&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;글 하나 쓰면 4가지 채널에 콘텐츠를 배포할 수 있습니다. 1인 크리에이터에게는 이만한 효율이 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;주의할 점&lt;/h2&gt;
&lt;h3&gt;AI 생성 콘텐츠 표시&lt;/h3&gt;
&lt;p&gt;유튜브는 AI로 만든 영상에 대해 &lt;strong&gt;AI 생성 콘텐츠임을 표시&lt;/strong&gt;하도록 권장하고 있습니다. YouTube Studio 업로드 시 &quot;변경되거나 합성된 콘텐츠&quot; 옵션에서 해당 사항을 체크하세요.&lt;/p&gt;
&lt;h3&gt;무료 계정 제한&lt;/h3&gt;
&lt;p&gt;무료 NotebookLM 계정은 생성 횟수에 제한이 있습니다. 한 노트북당 생성 가능한 동영상 수가 정해져 있으므로, 프롬프트를 잘 작성해서 한 번에 만족스러운 결과를 얻는 게 중요합니다.&lt;/p&gt;
&lt;h3&gt;한국어 지원&lt;/h3&gt;
&lt;p&gt;Video Overview는 한국어를 포함한 여러 언어를 지원합니다. 다만 Cinematic 형식은 현재 영어만 지원됩니다. 무료로 사용하는 Brief/Explainer는 한국어로 생성 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;NotebookLM Video Overview는 무료인가요?&lt;/h3&gt;
&lt;p&gt;Brief와 Explainer 형식은 무료로 사용할 수 있습니다. 구글 계정만 있으면 됩니다. Cinematic 형식만 Ultra 구독($250/월)이 필요하지만, 블로그 글을 유튜브 영상으로 만드는 용도라면 Explainer로 충분합니다.&lt;/p&gt;
&lt;h3&gt;블로그 글 URL을 넣으면 자동으로 전체 내용을 읽나요?&lt;/h3&gt;
&lt;p&gt;네, NotebookLM이 웹페이지의 텍스트 콘텐츠를 자동으로 추출합니다. 다만 이미지나 코드 블록의 세부 내용은 반영되지 않을 수 있으므로, 중요한 내용이 텍스트로 잘 작성되어 있는지 확인하세요.&lt;/p&gt;
&lt;h3&gt;생성된 동영상의 저작권은 누구에게 있나요?&lt;/h3&gt;
&lt;p&gt;NotebookLM으로 생성한 동영상의 저작권은 생성자에게 있습니다. 상업적 사용도 가능합니다. 다만 원본 블로그 글의 내용이 타인의 저작물을 포함하고 있다면 별도로 확인이 필요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;정리하면, 블로그 글을 유튜브 영상으로 만드는 과정은 이렇습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;블로그에 글을 쓴다&lt;/strong&gt; — 이미 하고 있는 일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NotebookLM에 URL을 넣고 Video Overview를 생성한다&lt;/strong&gt; — 5분 클릭&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다운로드해서 유튜브에 업로드한다&lt;/strong&gt; — 메타데이터만 잘 채우면 끝&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;핵심은 &lt;strong&gt;새로운 콘텐츠를 만드는 게 아니라, 이미 쓴 글을 다른 형태로 변환하는 것&lt;/strong&gt;입니다. 글 쓰는 시간은 이미 투자했으니, 추가 10분만 투자하면 유튜브 채널까지 운영할 수 있습니다.&lt;/p&gt;
&lt;p&gt;저도 이 블로그의 포스팅을 하나씩 영상으로 변환해볼 예정입니다. 진행 과정도 공유하겠습니다.&lt;/p&gt;
&lt;p&gt;여러분은 블로그 글을 다른 형태로 재활용하고 있으신가요? 댓글로 알려주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://notebooklm.google.com&quot;&gt;NotebookLM 공식 사이트&lt;/a&gt; — 구글 AI 리서치 도구&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/notebooklm/answer/16454555?hl=ko&quot;&gt;NotebookLM Video Overview 생성 가이드 (공식)&lt;/a&gt; — 동영상 개요 생성 방법&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.google/innovation-and-ai/products/notebooklm/generate-your-own-cinematic-video-overviews-in-notebooklm/&quot;&gt;Cinematic Video Overviews 출시 발표 (Google 공식 블로그)&lt;/a&gt; — 2026년 3월 신기능 소개&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/youtube/answer/57407&quot;&gt;YouTube Studio 고객센터&lt;/a&gt; — 동영상 업로드 가이드&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[블로그 대표이미지를 코드로 만드는 법 — HTML + Playwright PNG 변환]]></title><description><![CDATA[블로그 대표이미지 자동 생성 커버 — HTML + Playwright로 PNG 변환하는 방법 안녕하세요, 자바파커입니다. 블로그 포스팅을 쓸 때마다 고민되는 게 있습니다 — 대표이미지. "포토샵은 못 쓰고, Canva…]]></description><link>https://blog.javapark.kr/html-to-png-image-generation/</link><guid isPermaLink="false">https://blog.javapark.kr/html-to-png-image-generation/</guid><pubDate>Sat, 28 Mar 2026 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/html-to-png-image-generation.png&quot; alt=&quot;블로그 대표이미지 자동 생성 커버 — HTML + Playwright로 PNG 변환하는 방법&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;블로그 포스팅을 쓸 때마다 고민되는 게 있습니다 — &lt;strong&gt;대표이미지&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;포토샵은 못 쓰고, Canva는 매번 접속하기 귀찮고, 일관된 스타일을 유지하고 싶은데...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;HTML+CSS로 이미지를 디자인하고, Playwright로 PNG를 캡처하면 됩니다.&lt;/strong&gt; 이 블로그의 모든 대표이미지가 이 방식으로 만들어졌습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 HTML로 이미지를 만드는가?&lt;/h2&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;&lt;strong&gt;포토샵/피그마&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;자유도 높음&lt;/td&gt;
&lt;td&gt;매번 수동 작업, 라이선스 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Canva&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;쉽고 빠름&lt;/td&gt;
&lt;td&gt;템플릿 느낌, 일관성 유지 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTML+CSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;코드로 관리, 재사용, 자동화 가능&lt;/td&gt;
&lt;td&gt;CSS 지식 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;개발자에게 HTML+CSS 방식이 유리한 이유는 세 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;코드 = 템플릿&lt;/strong&gt; — 한 번 만들면 텍스트만 바꿔서 재사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;버전 관리&lt;/strong&gt; — Git으로 이미지 소스를 관리할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동화&lt;/strong&gt; — Claude Code에게 &quot;이 포스팅용 대표이미지 만들어줘&quot; 한 마디로 생성 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;전체 워크플로우&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;HTML+CSS 작성 → 브라우저에서 확인 → Playwright로 PNG 캡처 → 블로그에 사용&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;1. HTML 작성&lt;/td&gt;
&lt;td&gt;VS Code / Claude Code&lt;/td&gt;
&lt;td&gt;5~10분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 미리보기&lt;/td&gt;
&lt;td&gt;브라우저에서 파일 열기&lt;/td&gt;
&lt;td&gt;즉시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. PNG 캡처&lt;/td&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;즉시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;1단계: HTML+CSS로 이미지 디자인&lt;/h2&gt;
&lt;h3&gt;기본 구조 — 썸네일 (1200x630)&lt;/h3&gt;
&lt;p&gt;블로그 OG 이미지, 유튜브 썸네일용 비율입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ko&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
      &lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; border-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token selector&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1200px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 630px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          135deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          #0f172a 0%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          #1e293b 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          #0f172a 100%
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Segoe UI&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Malgun Gothic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sans-serif&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token selector&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #f1f5f9&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 48px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 800&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.3&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token selector&quot;&gt;h1 span&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #0ea5e9&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #8b5cf6&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-background-clip&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; transparent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;여기에 &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;제목&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;을&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;입력합니다&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이것만으로도 다크 배경에 그라데이션 텍스트가 들어간 이미지가 됩니다.&lt;/p&gt;
&lt;h3&gt;기본 구조 — 대표이미지 (1080x1080)&lt;/h3&gt;
&lt;p&gt;SNS 공유, 티스토리 대표이미지용 1:1 비율입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
  &lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; border-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1080px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1080px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1080px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1080px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* 나머지는 동일 */&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심:&lt;/strong&gt; 1:1 비율일 때는 &lt;code class=&quot;language-text&quot;&gt;html&lt;/code&gt;에도 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;overflow: hidden&lt;/code&gt;을 넣어야 캡처 시 여백이 생기지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;2단계: 디자인 요소 추가하기&lt;/h2&gt;
&lt;p&gt;기본 구조에 시각적 요소를 더하면 완성도가 올라갑니다. 이 블로그에서 실제로 사용하는 패턴을 공개합니다.&lt;/p&gt;
&lt;h3&gt;그리드 배경&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;body::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;inset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;14&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 165&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 233&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.06&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      transparent 1px
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;14&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 165&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 233&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.06&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transparent 1px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40px 40px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;반투명 격자 패턴으로 &quot;기술적인 느낌&quot;을 줍니다. 배경에 깔아두면 단순한 배경이 살아납니다.&lt;/p&gt;
&lt;h3&gt;글로우 오브(Glow Orb)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.orb&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;80px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.3&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.orb-1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 350px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 350px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #0ea5e9&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -100px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 150px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.orb-2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 280px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 280px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #8b5cf6&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -80px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;orb orb-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;orb orb-2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;blur&lt;/code&gt; 처리된 큰 원이 배경에 깔리면서 자연스러운 색감 변화를 만듭니다. 색상을 바꾸면 포스팅 주제별로 분위기를 다르게 줄 수 있습니다.&lt;/p&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;AI / 기술&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;#0ea5e9&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;#8b5cf6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;차분한 블루&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;경고 / 폐지&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;#f97316&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;#ef4444&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;긴장감 있는 오렌지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YouTube&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;#ef4444&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;#8b5cf6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레드 + 퍼플&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;#d4a574&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;#8b5cf6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;브라운 + 퍼플&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;배지(Badge)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.badge&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inline-block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;14&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 165&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 233&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.15&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;14&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 165&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 233&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #38bdf8&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 14px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 600&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 6px 16px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 20px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;letter-spacing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;text-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; uppercase&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;badge&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;SEO Guide&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&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;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;h1 span&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #0ea5e9&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #8b5cf6&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;-webkit-background-clip&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; transparent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;제목의 핵심 단어에만 적용하면 강조 효과가 확실합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3단계: Playwright로 PNG 캡처&lt;/h2&gt;
&lt;p&gt;HTML 파일을 브라우저에서 열어 확인한 후, Playwright로 캡처합니다.&lt;/p&gt;
&lt;h3&gt;Playwright MCP 사용 (Claude Code 환경)&lt;/h3&gt;
&lt;p&gt;Claude Code에서 Playwright MCP를 사용하면 대화 중에 바로 캡처할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. playwright_navigate — HTML 파일 열기 (뷰포트 크기 지정)
2. playwright_screenshot — PNG로 저장&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;썸네일 (1200x630)&lt;/strong&gt; 캡처:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;navigate: file:///경로/thumbnail.html (width: 1200, height: 630)
screenshot: savePng → thumbnail.png&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;대표이미지 (1080x1080)&lt;/strong&gt; 캡처:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;navigate: file:///경로/cover.html (width: 1080, height: 1080)
screenshot: savePng → cover.png&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Playwright CLI 사용 (수동)&lt;/h3&gt;
&lt;p&gt;Playwright가 설치되어 있다면 스크립트로도 캡처할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// capture.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; chromium &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;playwright&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; browser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; chromium&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;viewport&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;630&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;goto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;file:///경로/thumbnail.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;screenshot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;thumbnail.png&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; capture.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;캡처 시 주의할 점&lt;/h3&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;이미지가 2:1로 찍힘&lt;/td&gt;
&lt;td&gt;뷰포트와 body 크기 불일치&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;html&lt;/code&gt;에도 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;overflow: hidden&lt;/code&gt; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;하단에 여백 발생&lt;/td&gt;
&lt;td&gt;브라우저가 html에 추가 높이 부여&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;html { overflow: hidden }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;한글 깨짐&lt;/td&gt;
&lt;td&gt;폰트 미지정&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;font-family&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;&apos;Malgun Gothic&apos;&lt;/code&gt; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;그라데이션 텍스트 안 보임&lt;/td&gt;
&lt;td&gt;Firefox에서 미지원&lt;/td&gt;
&lt;td&gt;Chromium 사용 (Playwright 기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시 — 이 블로그의 이미지 시스템&lt;/h2&gt;
&lt;p&gt;이 블로그는 모든 포스팅에 2종류의 이미지를 만듭니다.&lt;/p&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;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;&lt;strong&gt;Thumbnail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~2:1&lt;/td&gt;
&lt;td&gt;1200x630&lt;/td&gt;
&lt;td&gt;OG 이미지, 유튜브 썸네일&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;*-thumbnail.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;td&gt;1080x1080&lt;/td&gt;
&lt;td&gt;티스토리 대표이미지, SNS&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;*-cover.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;디자인 일관성 유지 패턴&lt;/h3&gt;
&lt;p&gt;모든 이미지에 공통으로 적용하는 요소:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;배경&lt;/strong&gt; — 다크 그라데이션 (&lt;code class=&quot;language-text&quot;&gt;#0f172a&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;#1e293b&lt;/code&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; — 주제별 색상 2~3개&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;code class=&quot;language-text&quot;&gt;AI JAVAPARK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;텍스트와 오브 색상만 바꾸면 일관된 브랜딩을 유지하면서 포스팅별로 구분이 됩니다.&lt;/p&gt;
&lt;h3&gt;파일 구조&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;tistory/post/images/
├── post-name-thumbnail.html    ← 썸네일 소스
├── post-name-thumbnail.png     ← 썸네일 이미지
├── post-name-cover.html        ← 대표이미지 소스
└── post-name-cover.png         ← 대표이미지&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HTML 소스를 함께 보관하면 나중에 수정이 필요할 때 바로 편집하고 다시 캡처할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code와 함께 쓰면&lt;/h2&gt;
&lt;p&gt;이 방법의 진짜 장점은 &lt;strong&gt;Claude Code와 결합했을 때&lt;/strong&gt; 나타납니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;이 포스팅에 맞는 대표이미지 만들어줘&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 한 마디면 Claude Code가:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;포스팅 내용을 읽고&lt;/li&gt;
&lt;li&gt;주제에 맞는 색상과 레이아웃을 결정하고&lt;/li&gt;
&lt;li&gt;HTML+CSS를 작성하고&lt;/li&gt;
&lt;li&gt;Playwright로 PNG를 캡처합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;실제로 이 블로그의 포스팅 6개에 대한 썸네일 6개 + 대표이미지 6개, 총 12개의 이미지를 모두 이 방식으로 만들었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Playwright 없이 PNG로 변환할 수 있나요?&lt;/h3&gt;
&lt;p&gt;브라우저에서 HTML을 열고 개발자 도구의 &lt;strong&gt;디바이스 모드&lt;/strong&gt;에서 해상도를 맞춘 뒤 스크린샷을 찍는 방법도 있습니다. 다만 정확한 크기 제어와 자동화를 위해서는 Playwright가 훨씬 편합니다.&lt;/p&gt;
&lt;h3&gt;폰트를 웹폰트로 쓸 수 있나요?&lt;/h3&gt;
&lt;p&gt;네, &lt;code class=&quot;language-text&quot;&gt;&amp;lt;link&gt;&lt;/code&gt; 태그로 Google Fonts를 불러와서 사용할 수 있습니다. 다만 로컬 파일을 열 때 네트워크 요청이 필요하므로, Playwright 캡처 시 &lt;code class=&quot;language-text&quot;&gt;waitUntil: &apos;networkidle&apos;&lt;/code&gt; 옵션을 추가하면 폰트 로딩을 기다립니다.&lt;/p&gt;
&lt;h3&gt;다크 모드 말고 라이트 모드도 가능한가요?&lt;/h3&gt;
&lt;p&gt;물론입니다. &lt;code class=&quot;language-text&quot;&gt;background&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;#ffffff&lt;/code&gt;로, 텍스트를 &lt;code class=&quot;language-text&quot;&gt;#0f172a&lt;/code&gt;로 바꾸면 됩니다. 오브 색상의 &lt;code class=&quot;language-text&quot;&gt;opacity&lt;/code&gt;를 더 낮추면 라이트 배경에서도 자연스럽습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;정리하면, HTML로 블로그 이미지를 만드는 과정은 이렇습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTML+CSS로 디자인&lt;/strong&gt; — body 크기로 비율 고정, CSS로 시각 요소 구성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;브라우저에서 확인&lt;/strong&gt; — 파일을 열어 결과 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright로 PNG 캡처&lt;/strong&gt; — 정확한 크기로 이미지 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;포토샵 없이, 코드만으로, 일관된 브랜딩의 이미지를 만들 수 있습니다. 한 번 패턴을 잡아두면 새 포스팅마다 텍스트와 색상만 바꾸면 되니, 이미지 제작에 들이는 시간이 크게 줄어듭니다.&lt;/p&gt;
&lt;p&gt;여러분은 블로그 이미지를 어떻게 만들고 있나요? 댓글로 알려주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/screenshots&quot;&gt;Playwright 공식 문서 — Screenshots&lt;/a&gt; — 스크린샷 캡처 옵션 상세 가이드&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cssgradient.io/&quot;&gt;CSS Gradient Generator&lt;/a&gt; — 배경 그라데이션 생성 도구&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip&quot;&gt;MDN — background-clip: text&lt;/a&gt; — 그라데이션 텍스트 구현 방법&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fonts.google.com/&quot;&gt;Google Fonts&lt;/a&gt; — 무료 웹폰트&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Claude Code Skill 사용법 — 슬래시 명령어로 반복 업무 자동화]]></title><description><![CDATA[Claude Code Skill 사용법 커버 — 슬래시 명령어로 반복 업무 자동화 안녕하세요, 자바파커입니다. Claude Code…]]></description><link>https://blog.javapark.kr/skill/</link><guid isPermaLink="false">https://blog.javapark.kr/skill/</guid><pubDate>Sat, 28 Mar 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/claude-code-skill.png&quot; alt=&quot;Claude Code Skill 사용법 커버 — 슬래시 명령어로 반복 업무 자동화&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;Claude Code를 쓰다 보면 매번 같은 지시를 반복하는 순간이 옵니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;블로그 포스팅 써줘. 톤은 친근하게, 마크다운으로, 코드 예시 포함해서, 도입-본문-정리 구조로...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 긴 설명을, 이렇게 한 줄로 바꿀 수 있다면 어떨까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/blog-post Claude Code Skill 소개&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이게 바로 &lt;strong&gt;Skill&lt;/strong&gt;입니다. 실제로 이 글도 제가 만든 블로그 포스팅 스킬로 작성했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code Skill이란?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;반복되는 작업 지시를 파일로 저장해두고, 슬래시 명령어로 호출하는 기능&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;비유하자면, 신입에게 매번 말로 설명하던 업무 절차를 &lt;strong&gt;매뉴얼 문서로 만들어둔 것&lt;/strong&gt;과 같습니다. Claude가 그 매뉴얼을 읽고, 그대로 실행합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Skill vs Hook vs CLAUDE.md 차이점 비교&lt;/h2&gt;
&lt;p&gt;처음 접하면 헷갈리는 세 가지를 정리합니다.&lt;/p&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;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;프로젝트 전체에 적용되는 규칙&lt;/td&gt;
&lt;td&gt;사무실 벽에 붙은 공지사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;특정 작업을 위한 명령어&lt;/td&gt;
&lt;td&gt;업무 매뉴얼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이벤트 발생 시 자동 실행&lt;/td&gt;
&lt;td&gt;자동화 봇&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심 차이를 한 줄로 요약하면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt; — 항상 적용됩니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skill&lt;/strong&gt; — 내가 호출하거나, Claude가 판단해서 호출합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook&lt;/strong&gt; — 특정 이벤트에 자동으로 실행됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;SKILL.md 만드는 법 — 3단계&lt;/h2&gt;
&lt;h3&gt;1단계: 폴더와 파일 만들기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;~/.claude/skills/blog-post/
└── SKILL.md&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이게 끝입니다. &lt;code class=&quot;language-text&quot;&gt;SKILL.md&lt;/code&gt; 파일 하나만 있으면 됩니다.&lt;/p&gt;
&lt;p&gt;저장 위치에 따라 적용 범위가 달라집니다.&lt;/p&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;&lt;code class=&quot;language-text&quot;&gt;~/.claude/skills/&amp;lt;name&gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;내 모든 프로젝트&lt;/td&gt;
&lt;td&gt;개인 스킬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;.claude/skills/&amp;lt;name&gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;해당 프로젝트만&lt;/td&gt;
&lt;td&gt;팀 공유 (Git 커밋)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2단계: SKILL.md 작성하기&lt;/h3&gt;
&lt;p&gt;파일 구조는 &lt;strong&gt;YAML 프론트매터 + 마크다운 본문&lt;/strong&gt; 두 부분으로 되어 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;post
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 블로그 포스팅 초안을 작성합니다
&lt;span class=&quot;token key atrule&quot;&gt;disable-model-invocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;allowed-tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Grep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Glob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WebSearch
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 블로그 포스팅 작성 스킬&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## 기본 정보&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;블로그명&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AI JAVAPARK
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;필명&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 자바파커

&lt;span class=&quot;token comment&quot;&gt;## 작성 규칙&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 실전 경험 기반의 친근한 문체
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 어려운 개념은 비유나 실제 사례로 설명
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 도입 → 본문 → 정리 구조

&lt;span class=&quot;token comment&quot;&gt;## 포맷&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 마크다운 형식
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 코드 블록에는 언어 표기&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;---&lt;/code&gt;로 감싼 윗부분이 &lt;strong&gt;프론트매터&lt;/strong&gt;(설정), 아랫부분이 &lt;strong&gt;본문&lt;/strong&gt;(Claude에게 주는 지시)입니다.&lt;/p&gt;
&lt;h3&gt;3단계: 호출하기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/blog-post Claude Code Skill 소개&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&quot;Claude Code Skill 소개&quot;가 &lt;code class=&quot;language-text&quot;&gt;$ARGUMENTS&lt;/code&gt;로 전달되어, 스킬에 정의된 규칙대로 포스팅이 만들어집니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;SKILL.md 프론트매터 옵션 정리&lt;/h2&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;&lt;code class=&quot;language-text&quot;&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;슬래시 명령어 이름&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;blog-post&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스킬 설명&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;블로그 포스팅 초안을 작성합니다&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;disable-model-invocation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;면 수동 호출만 가능&lt;/td&gt;
&lt;td&gt;부작용 있는 작업에 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;allowed-tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용할 도구 제한&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Read, Grep, Glob&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;context&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fork&lt;/code&gt; 시 서브에이전트로 분리 실행&lt;/td&gt;
&lt;td&gt;메인 대화 오염 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt; 시 에이전트 타입&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Explore&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Plan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;실전 예시 — 블로그 포스팅 자동화 Skill&lt;/h2&gt;
&lt;p&gt;실제로 사용 중인 스킬 전체를 공개합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blog&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;post
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 블로그 포스팅 초안을 작성합니다. 주제를 전달하면 구조화된 마크다운 포스팅을 생성합니다.
&lt;span class=&quot;token key atrule&quot;&gt;disable-model-invocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;allowed-tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Grep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Glob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WebSearch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WebFetch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Agent
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 블로그 포스팅 작성 스킬&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## 기본 정보&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;블로그명&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AI JAVAPARK
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;필명&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 자바파커
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;플랫폼&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 티스토리
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;주요 독자&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 개발자&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AI/IT에 관심 있는 사람들

&lt;span class=&quot;token comment&quot;&gt;## 작성 규칙&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;### 톤 &amp;amp; 스타일&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 실전 경험 기반의 친근한 문체 (&quot;~합니다&quot; 체)
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 어려운 개념은 비유나 실제 사례로 설명
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 독자가 바로 따라할 수 있는 실용적 내용 중심
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 불필요한 서론 최소화&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 핵심부터 진입

&lt;span class=&quot;token comment&quot;&gt;### 구조&lt;/span&gt;
1. &lt;span class=&quot;token important&quot;&gt;**도입**&lt;/span&gt; — 왜 이 주제를 다루는지 (2~3문장)
2. &lt;span class=&quot;token important&quot;&gt;**본문**&lt;/span&gt; — 개념 설명 + 실전 예시 + 코드/설정 예시
3. &lt;span class=&quot;token important&quot;&gt;**정리**&lt;/span&gt; — 핵심 요약 또는 다음 단계 제안

&lt;span class=&quot;token comment&quot;&gt;### 포맷&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 마크다운 형식
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 코드 블록에는 언어 표기
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 적절한 소제목으로 스캔 가능하게 구성
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 긴 설명보다 표&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 목록&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 코드 예시 활용

&lt;span class=&quot;token comment&quot;&gt;## 작업 흐름&lt;/span&gt;
1. 주제($ARGUMENTS)를 받으면 먼저 포스팅 아웃라인을 잡는다
2. 필요하면 WebSearch로 최신 정보를 확인한다
3. 프로젝트 내 관련 코드/설정이 있으면 참조한다
4. 마크다운 초안을 작성한다
5. 초안을 프로젝트 루트에 posts/ 디렉토리 아래 저장한다&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 스킬 덕분에 &lt;code class=&quot;language-text&quot;&gt;/blog-post 주제&lt;/code&gt; 한 줄이면 블로그 톤, 구조, 포맷을 매번 설명하지 않아도 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code Skill 활용 예시 — 코드 리뷰, 이슈 처리&lt;/h2&gt;
&lt;h3&gt;코드 리뷰 스킬&lt;/h3&gt;
&lt;p&gt;PR을 서브에이전트로 분석합니다. &lt;code class=&quot;language-text&quot;&gt;!&lt;/code&gt;command`` 문법으로 쉘 명령어 결과를 동적으로 주입하는 게 포인트입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; review
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; PR 코드 리뷰를 수행합니다
&lt;span class=&quot;token key atrule&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fork
&lt;span class=&quot;token key atrule&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Explore
&lt;span class=&quot;token key atrule&quot;&gt;allowed-tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Bash(gh &lt;span class=&quot;token important&quot;&gt;*)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Grep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Glob
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## PR 컨텍스트&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;PR diff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;!&lt;/span&gt;`gh pr diff`
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;변경 파일&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;!&lt;/span&gt;`gh pr diff &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;only`

&lt;span class=&quot;token comment&quot;&gt;## 리뷰 기준&lt;/span&gt;
1. 버그 가능성이 있는 코드
2. 성능 이슈
3. 보안 취약점
4. 컨벤션 위반&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;GitHub 이슈 처리 스킬&lt;/h3&gt;
&lt;p&gt;이슈 번호만 넘기면 분석부터 커밋까지 처리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;issue
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; GitHub 이슈를 분석하고 수정합니다
&lt;span class=&quot;token key atrule&quot;&gt;disable-model-invocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

GitHub 이슈 &lt;span class=&quot;token comment&quot;&gt;#$ARGUMENTS 를 처리합니다:&lt;/span&gt;

1. 이슈 내용을 읽고 요구사항 파악
2. 관련 코드 탐색
3. 수정 구현
4. 테스트 작성
5. 커밋 생성&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;Claude Code Skill 작성 팁 5가지&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;disable-model-invocation: true&lt;/code&gt;를 습관적으로 쓰세요.&lt;/strong&gt;
배포, 커밋, 메시지 전송 등 부작용이 있는 스킬에 설정하지 않으면, Claude가 대화 중 알아서 호출할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;description을 잘 쓰세요.&lt;/strong&gt;
Claude가 자동 호출 여부를 판단할 때 이 설명을 봅니다. 모호하면 엉뚱한 타이밍에 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SKILL.md는 500줄 이내로.&lt;/strong&gt;
길어지면 별도 파일로 분리하고 SKILL.md에서 참조하세요. 스킬 디렉토리 안에 &lt;code class=&quot;language-text&quot;&gt;examples/&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;templates/&lt;/code&gt; 같은 하위 폴더를 두면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;$ARGUMENTS&lt;/code&gt;로 입력값을 받으세요.&lt;/strong&gt;
&lt;code class=&quot;language-text&quot;&gt;/skill-name 인자값&lt;/code&gt; 형태로 호출하면 &lt;code class=&quot;language-text&quot;&gt;$ARGUMENTS&lt;/code&gt;에 전달됩니다. &lt;code class=&quot;language-text&quot;&gt;$0&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;$1&lt;/code&gt;로 개별 인자도 접근 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt;로 대화를 깔끔하게.&lt;/strong&gt;
리서치나 분석처럼 긴 작업은 서브에이전트로 분리하면 메인 대화가 오염되지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;Skill과 프롬프트 템플릿은 뭐가 다른가요?&lt;/h3&gt;
&lt;p&gt;프롬프트 템플릿은 텍스트를 복사-붙여넣기 하는 방식입니다. Skill은 Claude Code가 직접 파일을 읽고 실행하기 때문에, 도구 사용 제한(&lt;code class=&quot;language-text&quot;&gt;allowed-tools&lt;/code&gt;), 자동 호출 제어(&lt;code class=&quot;language-text&quot;&gt;disable-model-invocation&lt;/code&gt;), 서브에이전트 분리(&lt;code class=&quot;language-text&quot;&gt;context: fork&lt;/code&gt;) 같은 세밀한 제어가 가능합니다.&lt;/p&gt;
&lt;h3&gt;Skill을 팀원과 공유할 수 있나요?&lt;/h3&gt;
&lt;p&gt;프로젝트 루트의 &lt;code class=&quot;language-text&quot;&gt;.claude/skills/&lt;/code&gt; 에 저장하면 Git으로 커밋할 수 있습니다. 팀원이 같은 저장소를 클론하면 동일한 슬래시 명령어를 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Skill이 너무 길어지면 어떻게 하나요?&lt;/h3&gt;
&lt;p&gt;SKILL.md는 500줄 이내로 유지하는 것이 좋습니다. 길어지면 스킬 디렉토리 안에 &lt;code class=&quot;language-text&quot;&gt;examples/&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;templates/&lt;/code&gt; 같은 하위 폴더를 만들고, SKILL.md에서 해당 파일을 참조하도록 작성하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Skill은 결국 **&quot;Claude에게 주는 업무 매뉴얼을 파일로 저장한 것&quot;**입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SKILL.md&lt;/code&gt; 파일 하나 만들고&lt;/li&gt;
&lt;li&gt;프론트매터에 이름과 설명 적고&lt;/li&gt;
&lt;li&gt;본문에 작업 지시를 마크다운으로 작성하면&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그때부터 &lt;code class=&quot;language-text&quot;&gt;/name&lt;/code&gt;으로 호출할 수 있습니다.&lt;/p&gt;
&lt;p&gt;반복하는 작업이 있다면, 오늘 바로 하나 만들어보세요. 한번 만들어두면 다시는 같은 설명을 반복하지 않아도 됩니다.&lt;/p&gt;
&lt;p&gt;여러분은 어떤 반복 업무를 스킬로 만들고 싶으신가요? 댓글로 알려주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/skills&quot;&gt;Claude Code 공식 문서 — Skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/memory&quot;&gt;Claude Code 공식 문서 — CLAUDE.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/hooks&quot;&gt;Claude Code 공식 문서 — Hooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[AI로 웹서비스 만드는 법 — Claude Code + Supabase 실전 가이드]]></title><description><![CDATA[AI로 웹서비스 만들기 커버 — Claude Code + Supabase + Vercel 1인 개발 가이드 안녕하세요, 자바파커입니다. 요즘 개발 환경이 빠르게 바뀌고 있습니다. 예전에는 웹서비스 하나 만들려면 프론트엔드, 백엔드, DB…]]></description><link>https://blog.javapark.kr/ai-driven-web-development/</link><guid isPermaLink="false">https://blog.javapark.kr/ai-driven-web-development/</guid><pubDate>Sat, 28 Mar 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://github.com/javapark/blog-images/releases/download/images-v1/ai-driven-web-development.png&quot; alt=&quot;AI로 웹서비스 만들기 커버 — Claude Code + Supabase + Vercel 1인 개발 가이드&quot;&gt;&lt;/p&gt;
&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;요즘 개발 환경이 빠르게 바뀌고 있습니다. 예전에는 웹서비스 하나 만들려면 프론트엔드, 백엔드, DB, 배포까지 혼자 다 해야 했습니다. 지금은 다릅니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;아이디어만 있으면, AI와 함께 웹서비스를 만들 수 있는 시대&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;과장이 아닙니다. 실제로 저는 &lt;strong&gt;Claude Code + GitHub + Supabase&lt;/strong&gt; 조합으로 웹서비스를 개발하고 있고, 이 프로세스를 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 웹개발 도구 조합 — Claude Code, GitHub, Supabase&lt;/h2&gt;
&lt;p&gt;먼저 각 도구의 역할을 정리합니다.&lt;/p&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;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI 코딩 에이전트 — 설계, 구현, 디버깅&lt;/td&gt;
&lt;td&gt;24시간 일하는 시니어 개발자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;코드 저장소 + 버전 관리 + 배포&lt;/td&gt;
&lt;td&gt;프로젝트의 금고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Supabase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;백엔드 올인원 (DB, 인증, API, 스토리지)&lt;/td&gt;
&lt;td&gt;서버 없이 쓰는 백엔드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 세 가지를 조합하면, 1인 개발자가 풀스택 웹서비스를 만들 수 있는 환경이 됩니다. 서버를 직접 관리할 필요도, 인증 로직을 처음부터 짤 필요도 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 웹서비스 개발 프로세스 — 5단계&lt;/h2&gt;
&lt;p&gt;제가 실제로 사용하는 프로세스를 단계별로 정리합니다.&lt;/p&gt;
&lt;h3&gt;1단계: 기획 — Claude Code와 대화하기&lt;/h3&gt;
&lt;p&gt;코드를 한 줄도 작성하기 전에, Claude Code에게 아이디어를 설명합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;할 일 관리 웹앱을 만들려고 합니다.
사용자 로그인, 할 일 CRUD, 카테고리 분류 기능이 필요합니다.
기술 스택은 Next.js + Supabase로 가려고 하는데, 어떻게 구조를 잡으면 좋을까요?&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code는 단순히 코드만 생성하는 게 아닙니다. 프로젝트 구조를 제안하고, 테이블 설계를 잡아주고, 필요한 패키지까지 알려줍니다.&lt;/p&gt;
&lt;p&gt;이 단계에서 중요한 건 &lt;strong&gt;&quot;무엇을 만들 것인가&quot;를 명확히 하는 것&lt;/strong&gt;입니다. AI는 방향을 알려주면 알아서 길을 찾지만, 방향이 모호하면 엉뚱한 곳으로 갑니다.&lt;/p&gt;
&lt;h3&gt;2단계: 프로젝트 셋업 — GitHub + Supabase 초기화&lt;/h3&gt;
&lt;h4&gt;GitHub 저장소 생성&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;gh repo create my-todo-app &lt;span class=&quot;token parameter variable&quot;&gt;--public&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--clone&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-todo-app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Supabase 프로젝트 생성&lt;/h4&gt;
&lt;p&gt;Supabase 대시보드에서 프로젝트를 만들면 바로 사용할 수 있는 것들이 생깁니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL 데이터베이스&lt;/strong&gt; — 테이블 만들면 자동으로 REST API 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth&lt;/strong&gt; — 이메일, Google, GitHub 로그인 내장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt; — 파일 업로드/다운로드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Realtime&lt;/strong&gt; — 데이터 변경 시 실시간 동기화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;별도의 백엔드 서버 없이, 프론트엔드에서 직접 Supabase를 호출하는 구조입니다.&lt;/p&gt;
&lt;h4&gt;환경 변수 설정&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .env.local&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https://xxxxx.supabase.co
&lt;span class=&quot;token assign-left variable&quot;&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;eyJhbGciOiJIUzI1NiIs&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3단계: 개발 — Claude Code가 코드를 쓴다&lt;/h3&gt;
&lt;p&gt;여기서부터 Claude Code의 진가가 발휘됩니다.&lt;/p&gt;
&lt;h4&gt;DB 테이블 설계&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Supabase에 할 일 관리용 테이블을 만들어줘.
사용자별로 할 일을 관리하고, 카테고리 분류가 가능해야 해.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code가 SQL을 작성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- categories 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; categories &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id uuid &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id uuid &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;cascade&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  name &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at timestamptz &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- todos 테이블&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; todos &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id uuid &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  user_id uuid &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;cascade&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  category_id uuid &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; categories&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  title &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  completed &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  created_at timestamptz &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- RLS(Row Level Security) 활성화&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; todos &lt;span class=&quot;token keyword&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;level&lt;/span&gt; security&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; categories &lt;span class=&quot;token keyword&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;level&lt;/span&gt; security&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- 본인 데이터만 접근 가능&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; policy &lt;span class=&quot;token string&quot;&gt;&quot;Users can manage own todos&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; todos &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; policy &lt;span class=&quot;token string&quot;&gt;&quot;Users can manage own categories&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; categories &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Supabase의 &lt;strong&gt;RLS(Row Level Security)&lt;/strong&gt; 는 꼭 설정해야 합니다. 안 그러면 모든 사용자가 다른 사람의 데이터를 볼 수 있습니다.&lt;/p&gt;
&lt;h4&gt;프론트엔드 구현&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Next.js App Router 구조로 할 일 목록 페이지를 만들어줘.
Supabase에서 데이터 가져오고, 추가/삭제/완료 토글 기능이 필요해.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code가 컴포넌트, API 호출, 상태 관리까지 한 번에 작성합니다. 저는 결과를 확인하고, 수정이 필요한 부분만 피드백합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;완료된 할 일은 아래쪽으로 내려가게 해줘.&quot;
&quot;카테고리 필터 버튼 추가해줘.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 식으로 &lt;strong&gt;대화하면서 개발을 진행&lt;/strong&gt;합니다. 코드를 직접 쓰는 시간보다, 원하는 동작을 설명하는 시간이 더 깁니다.&lt;/p&gt;
&lt;h3&gt;4단계: 버전 관리 — GitHub으로 기록&lt;/h3&gt;
&lt;p&gt;개발 중간중간 커밋을 남깁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: todo CRUD 기능 구현&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Claude Code에게 &lt;code class=&quot;language-text&quot;&gt;/commit&lt;/code&gt; 명령어를 사용하면 변경 내용을 분석해서 적절한 커밋 메시지까지 작성해줍니다.&lt;/p&gt;
&lt;h3&gt;5단계: 배포 — Vercel 또는 GitHub Pages&lt;/h3&gt;
&lt;p&gt;Next.js 프로젝트라면 Vercel에 연결하는 게 가장 간단합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vercel에 GitHub 저장소 연결&lt;/li&gt;
&lt;li&gt;환경 변수(Supabase URL, Key) 설정&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git push&lt;/code&gt;만 하면 자동 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;AI 개발의 핵심 — 사람과 AI의 역할 분담&lt;/h2&gt;
&lt;p&gt;제가 경험한 AI 기반 개발의 핵심은 &lt;strong&gt;역할 분담&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;내가 하는 일&lt;/th&gt;
&lt;th&gt;Claude Code가 하는 일&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;무엇을 만들지 결정&lt;/td&gt;
&lt;td&gt;어떻게 만들지 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요구사항 설명&lt;/td&gt;
&lt;td&gt;코드 작성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결과 확인 및 피드백&lt;/td&gt;
&lt;td&gt;수정 및 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최종 판단&lt;/td&gt;
&lt;td&gt;대안 제시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;비유하자면, 저는 &lt;strong&gt;건축주&lt;/strong&gt;이고 Claude Code는 &lt;strong&gt;건축가&lt;/strong&gt;입니다. 건축주가 &quot;3층짜리 카페를 짓고 싶어요&quot;라고 말하면, 건축가가 도면을 그리고 시공합니다. 건축주가 벽돌을 쌓을 필요는 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Supabase vs Firebase — 백엔드 서비스 비교&lt;/h2&gt;
&lt;p&gt;백엔드 서비스는 여러 선택지가 있습니다. 비교해봤습니다.&lt;/p&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;&lt;strong&gt;Supabase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 기반, 오픈소스, REST/GraphQL 자동 생성&lt;/td&gt;
&lt;td&gt;관계형 데이터, SQL 선호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firebase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NoSQL(Firestore), Google 생태계&lt;/td&gt;
&lt;td&gt;빠른 프로토타이핑, 모바일 앱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PocketBase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go 기반 단일 바이너리, 셀프호스팅&lt;/td&gt;
&lt;td&gt;가벼운 프로젝트, 직접 서버 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;제가 Supabase를 선택한 이유는 세 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SQL을 쓸 수 있다&lt;/strong&gt; — NoSQL보다 익숙하고, 복잡한 쿼리가 가능합니다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무료 티어가 넉넉하다&lt;/strong&gt; — 개인 프로젝트에 충분한 용량&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code와 궁합이 좋다&lt;/strong&gt; — SQL과 REST API를 AI가 잘 다룹니다&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;AI 웹개발 실전 팁 3가지&lt;/h2&gt;
&lt;h3&gt;Supabase RLS는 반드시 설정하세요&lt;/h3&gt;
&lt;p&gt;Supabase는 기본적으로 프론트엔드에서 직접 DB를 호출합니다. RLS 없이 배포하면 &lt;strong&gt;누구나 API로 모든 데이터에 접근할 수 있습니다.&lt;/strong&gt; 이건 실수가 아니라 보안 사고입니다.&lt;/p&gt;
&lt;h3&gt;Claude Code에게 컨텍스트를 충분히 주세요&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 나쁜 예
&quot;로그인 기능 만들어줘&quot;

# 좋은 예
&quot;Supabase Auth를 사용해서 이메일/비밀번호 로그인 기능을 만들어줘.
Next.js App Router 구조이고, 로그인 후 /dashboard로 리다이렉트해야 해.
미인증 사용자가 /dashboard에 접근하면 /login으로 보내줘.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;구체적으로 설명할수록, 수정 횟수가 줄어듭니다.&lt;/p&gt;
&lt;h3&gt;Git 커밋은 자주 하세요&lt;/h3&gt;
&lt;p&gt;AI와 개발하면 코드가 빠르게 바뀝니다. 중간중간 커밋하지 않으면, 문제가 생겼을 때 되돌릴 지점이 없습니다. &quot;동작하는 상태&quot;가 확인될 때마다 커밋하는 습관을 들이세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 묻는 질문 (FAQ)&lt;/h2&gt;
&lt;h3&gt;코딩을 몰라도 AI로 웹서비스를 만들 수 있나요?&lt;/h3&gt;
&lt;p&gt;기본적인 웹 개념(HTML, API 등)을 이해하면 충분합니다. Claude Code가 코드를 작성하고, 여러분은 &quot;무엇을 만들지&quot; 설명하는 역할입니다. 다만, 결과물을 검토하고 디버깅할 때 기초 지식이 있으면 훨씬 수월합니다.&lt;/p&gt;
&lt;h3&gt;이 조합으로 개발하면 비용이 얼마나 드나요?&lt;/h3&gt;
&lt;p&gt;Supabase 무료 티어(500MB DB, 1GB 스토리지), Vercel 무료 플랜, GitHub 무료 저장소를 사용하면 &lt;strong&gt;호스팅 비용은 0원&lt;/strong&gt;입니다. Claude Code 구독 비용만 발생합니다. 개인 프로젝트나 사이드 프로젝트에는 충분한 수준입니다.&lt;/p&gt;
&lt;h3&gt;Claude Code 말고 다른 AI 코딩 도구도 사용할 수 있나요?&lt;/h3&gt;
&lt;p&gt;GitHub Copilot, Cursor 등 다른 AI 코딩 도구도 비슷한 프로세스로 활용할 수 있습니다. 다만 Claude Code는 프로젝트 전체 맥락을 이해하고 대화 기반으로 개발을 진행할 수 있다는 점에서 이 프로세스에 가장 잘 맞습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;정리하면, AI를 이용한 웹서비스 개발 프로세스는 이렇습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기획&lt;/strong&gt; — Claude Code와 대화하며 구조 설계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;셋업&lt;/strong&gt; — GitHub 저장소 + Supabase 프로젝트 초기화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발&lt;/strong&gt; — Claude Code가 코드 작성, 나는 피드백&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;관리&lt;/strong&gt; — GitHub으로 버전 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배포&lt;/strong&gt; — Vercel 연결 후 자동 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;핵심은 &lt;strong&gt;&quot;코드를 직접 쓰는 것&quot;에서 &quot;무엇을 만들지 설명하는 것&quot;으로 역할이 바뀐다&lt;/strong&gt;는 점입니다.&lt;/p&gt;
&lt;p&gt;개발 경험이 많으면 더 정확한 지시를 할 수 있고, 경험이 적어도 AI와 대화하면서 배워갈 수 있습니다. 어느 쪽이든, 혼자 처음부터 끝까지 코딩하던 시대와는 확실히 다릅니다.&lt;/p&gt;
&lt;p&gt;여러분은 AI와 함께 어떤 서비스를 만들어보고 싶으신가요? 댓글로 알려주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/overview&quot;&gt;Claude Code 공식 문서&lt;/a&gt; — Anthropic의 AI 코딩 에이전트 소개 및 사용법&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.com/docs&quot;&gt;Supabase 공식 문서&lt;/a&gt; — 데이터베이스, 인증, 스토리지 설정 가이드&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/docs&quot;&gt;Vercel 공식 문서&lt;/a&gt; — Next.js 배포 및 환경 변수 설정&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app&quot;&gt;Next.js App Router 가이드&lt;/a&gt; — App Router 구조와 라우팅&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Ruff를 이용한 python 코드 품질 관리]]></title><description><![CDATA[Ruff 소개 최근 Python 개발자들 사이에서 Ruff가 많은 주목을 받고 있습니다. Ruff는 Python…]]></description><link>https://blog.javapark.kr/ruff/</link><guid isPermaLink="false">https://blog.javapark.kr/ruff/</guid><pubDate>Sun, 28 Jan 2024 11:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Ruff 소개&lt;/h2&gt;
&lt;p&gt;최근 Python 개발자들 사이에서 Ruff가 많은 주목을 받고 있습니다. Ruff는 Python 코드의 품질을 향상시키는 데 도움이 되는 강력한 린트 도구입니다. 린트는 코딩 스타일, 프로그래밍 오류, 버그, 복잡한 구조 등을 분석하여 개선 방향을 제시하는 소프트웨어 도구입니다. Ruff는 빠른 성능과 사용 편의성으로 인해 많은 Python 개발자들에게 선택받고 있습니다.&lt;/p&gt;
&lt;h2&gt;Ruff의 장점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;빠른 성능&lt;/strong&gt;: Ruff는 다른 린트 도구보다 빠른 분석 속도를 제공합니다. 이는 대규모 프로젝트에서도 효율적으로 사용할 수 있음을 의미합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용 용이성&lt;/strong&gt;: 설치 및 구성이 간단하며, 쉽게 사용할 수 있는 명령어 인터페이스를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동화&lt;/strong&gt;: Ruff는 코드 작성 과정에서 자동으로 문제를 탐지하고 수정 제안을 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Ruff 설치 및 사용&lt;/h2&gt;
&lt;h3&gt;설치 방법&lt;/h3&gt;
&lt;p&gt;Ruff는 Python의 패키지 관리자인 pip를 통해 쉽게 설치할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; ruff&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;사용 방법&lt;/h3&gt;
&lt;p&gt;Ruff를 사용하여 프로젝트의 코드를 분석하는 것은 매우 간단합니다. 터미널에서 다음 명령을 실행하기만 하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ruff path/to/your/code&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;VS Code에서의 Python 코딩&lt;/h2&gt;
&lt;p&gt;Visual Studio Code (VS Code)는 다양한 프로그래밍 언어를 지원하는 인기 있는 코드 편집기입니다. Python 개발에도 매우 적합하며, Ruff와 같은 도구를 통합하여 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;VS Code 설정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Python 확장 설치&lt;/strong&gt;: VS Code의 마켓플레이스에서 Python 확장을 설치합니다. 이 확장은 Python 개발에 필요한 다양한 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ruff 통합&lt;/strong&gt;: Ruff는 VS Code의 설정을 통해 Python 개발 환경에 쉽게 통합될 수 있습니다. 이를 통해 코드를 작성하는 동안 실시간으로 피드백을 받을 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Python 코딩 예제&lt;/h3&gt;
&lt;p&gt;다음은 VS Code에서 Python 코드를 작성하고 Ruff를 사용하여 코드 품질을 관리하는 간단한 예제입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 예제 코드&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;example_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

example_function&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 코드를 VS Code에서 작성하고 Ruff를 실행하면, Ruff는 코드 스타일과 구조상의 문제를 지적해 줄 것입니다. 예를 들어, 함수 내의 변수와 연산자 사이의 공백, 함수 정의와 호출 사이의 공백 등을 조정할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이러한 방식으로 Ruff와 VS Code를 활용하면 Python 개발 과정에서 코드 품질을 지속적으로 관리하고 개선할 수 있습니다. 더 나은 코드 품질로 안정적이고 효율적인 Python 애플리케이션 개발을 지향하는 개발자들에게 이러한 도구는 매우 유용합니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pipenv 현실 점검 — 2026 현재 써야 할까? (uv·poetry 와 비교)]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Pipenv 쓰고 계신가요?  돌려놓고 커피 한 잔 마시고 와도 아직 끝나 있지 않은 그 답답함, 저도 겪어봤습니다." 결론부터 말씀드리면 — pipenv 는 202…]]></description><link>https://blog.javapark.kr/pipenv/</link><guid isPermaLink="false">https://blog.javapark.kr/pipenv/</guid><pubDate>Sun, 28 Jan 2024 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Pipenv 쓰고 계신가요? &lt;code class=&quot;language-text&quot;&gt;pipenv lock&lt;/code&gt; 돌려놓고 커피 한 잔 마시고 와도 아직 끝나 있지 않은 그 답답함, 저도 겪어봤습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;pipenv 는 2026 현재 여전히 &quot;동작&quot;은 합니다. 하지만 신규 프로젝트에 권장하긴 어렵습니다.&lt;/strong&gt; 2024~2025 를 거치며 커뮤니티의 무게 중심은 완전히 &lt;strong&gt;uv(Astral)&lt;/strong&gt; 와 &lt;strong&gt;poetry&lt;/strong&gt; 쪽으로 이동했고, pipenv 는 사실상 &quot;레거시 유지&quot; 단계에 들어섰습니다.&lt;/p&gt;
&lt;p&gt;그렇다고 이 글이 &quot;pipenv 욕하는 글&quot;은 아닙니다. 아직도 pipenv 로 돌아가는 프로젝트는 많고, 당장 바꿀 수 없는 현실도 이해합니다. 그래서 이번 글에서는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pipenv 가 어떤 문제를 풀려고 태어났는지 (역사적 맥락)&lt;/li&gt;
&lt;li&gt;2026 현재 유지보수 상태가 정말 어떤지&lt;/li&gt;
&lt;li&gt;기본 사용법 (레거시 프로젝트 유지보수용 레퍼런스)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uv / poetry / pip-tools 와의 솔직한 비교표&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipfile → pyproject.toml 마이그레이션 체크리스트&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;까지 한 번에 정리하겠습니다.&lt;/p&gt;
&lt;h2&gt;Pipenv 는 어떤 문제를 풀려고 태어났나&lt;/h2&gt;
&lt;p&gt;2017 년 Kenneth Reitz(그 유명한 &lt;code class=&quot;language-text&quot;&gt;requests&lt;/code&gt; 저자) 가 내놓은 pipenv 는 그 당시로선 꽤 혁신적인 아이디어였습니다. 그 전까지 파이썬 개발자는 이걸 다 따로 관리했거든요:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;virtualenv&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;venv&lt;/code&gt; 로 가상환경 생성&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;pip install&lt;/code&gt; 로 패키지 설치&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;pip freeze &gt; requirements.txt&lt;/code&gt; 로 버전 고정&lt;/li&gt;
&lt;li&gt;개발용/배포용 분리는 &lt;code class=&quot;language-text&quot;&gt;requirements-dev.txt&lt;/code&gt; 로 수동 관리&lt;/li&gt;
&lt;li&gt;락파일? 그런 거 없음. &lt;code class=&quot;language-text&quot;&gt;requirements.txt&lt;/code&gt; 가 전부.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pipenv 는 이걸 &lt;strong&gt;하나의 도구로 묶고&lt;/strong&gt;, Node.js 의 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;package-lock.json&lt;/code&gt; 같은 경험을 파이썬에도 가져오려 했습니다. &lt;code class=&quot;language-text&quot;&gt;Pipfile&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;Pipfile.lock&lt;/code&gt; 조합이 그거죠.&lt;/p&gt;
&lt;p&gt;한때 PyPA(Python Packaging Authority) 가 공식적으로 추천하기도 했고, 2018~2020 즈음에는 &quot;파이썬 프로젝트 관리의 표준이 될 것&quot;이라는 기대도 받았습니다.&lt;/p&gt;
&lt;h2&gt;2026 현재 유지보수 상태 — 솔직하게&lt;/h2&gt;
&lt;p&gt;자, 여기서부터는 현실입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;릴리즈 주기&lt;/strong&gt;: 2023 년 이후 릴리즈 빈도가 급격히 떨어졌고, 2024~2025 에는 보안 패치 수준의 작은 릴리즈 위주였습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이슈 트래커&lt;/strong&gt;: 수백 개의 이슈가 열린 채 방치되어 있고, PR 리뷰도 지연이 일상화되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;pipenv lock&lt;/code&gt; 은 여전히 &lt;strong&gt;엄청 느립니다&lt;/strong&gt;. 의존성이 많은 프로젝트에서는 수 분~수십 분이 걸리기도 합니다. 같은 작업을 &lt;code class=&quot;language-text&quot;&gt;uv lock&lt;/code&gt; 으로 하면 초 단위로 끝납니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyPA 공식 추천&lt;/strong&gt;: 이제 pipenv 를 &quot;유일한 정답&quot;으로 미는 톤이 아닙니다. &lt;code class=&quot;language-text&quot;&gt;pip&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pip-tools&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;poetry&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;hatch&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pdm&lt;/code&gt; 등이 병렬로 소개되며, 사실상 &lt;strong&gt;&quot;고르세요&quot;&lt;/strong&gt; 단계로 바뀌었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;업계 분위기&lt;/strong&gt;: 2025 년 기준 신규 파이썬 프로젝트 대부분이 &lt;strong&gt;uv&lt;/strong&gt; 또는 &lt;strong&gt;poetry&lt;/strong&gt; 로 시작하고, 기업 레포에서도 점차 마이그레이션 중입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한 줄 요약: &lt;strong&gt;pipenv 는 죽지 않았지만, 성장은 멈췄습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;그래도 써야 한다면 — 기본 사용법&lt;/h2&gt;
&lt;p&gt;기존 레거시 프로젝트를 유지보수해야 하거나, 팀 전체가 아직 pipenv 를 쓰는 상황이라면 기본기는 알고 있어야 합니다.&lt;/p&gt;
&lt;h3&gt;설치&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--user&lt;/span&gt; pipenv&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;시스템 파이썬을 더럽히지 않으려면 &lt;code class=&quot;language-text&quot;&gt;pipx install pipenv&lt;/code&gt; 가 더 깔끔합니다.&lt;/p&gt;
&lt;h3&gt;프로젝트 시작&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; myproject &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; myproject
pipenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Pipfile&lt;/code&gt; 이 자동 생성되고, 현재 디렉터리에 연결된 가상환경이 만들어집니다.&lt;/p&gt;
&lt;h3&gt;패키지 설치 / 개발 의존성 분리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 런타임 의존성&lt;/span&gt;
pipenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; requests

&lt;span class=&quot;token comment&quot;&gt;# 개발 전용 (테스트, 린터 등)&lt;/span&gt;
pipenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--dev&lt;/span&gt; pytest ruff&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;스크립트 실행&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pipenv run python main.py
pipenv run pytest&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pipenv shell&lt;/code&gt; 로 가상환경에 진입할 수도 있지만, 서브셸이 생성되는 방식이라 혼란스러울 때가 있어 &lt;code class=&quot;language-text&quot;&gt;pipenv run&lt;/code&gt; 사용을 더 권장합니다.&lt;/p&gt;
&lt;h3&gt;락파일 생성 / 동기화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pipenv lock     &lt;span class=&quot;token comment&quot;&gt;# Pipfile.lock 생성 (느림 주의)&lt;/span&gt;
pipenv &lt;span class=&quot;token function&quot;&gt;sync&lt;/span&gt;     &lt;span class=&quot;token comment&quot;&gt;# Pipfile.lock 기준으로 정확히 동일하게 설치 (CI/배포용)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CI 에서는 &lt;code class=&quot;language-text&quot;&gt;pipenv install&lt;/code&gt; 대신 &lt;strong&gt;반드시 &lt;code class=&quot;language-text&quot;&gt;pipenv sync&lt;/code&gt;&lt;/strong&gt; 를 쓰세요. 락파일을 깨지 않고 재현 가능한 빌드를 보장합니다.&lt;/p&gt;
&lt;h2&gt;Pipfile / Pipfile.lock 구조&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;toml&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Pipfile&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://pypi.org/simple&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;verify_ssl&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pypi&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;packages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;requests&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;fastapi&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;=0.110&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;dev-packages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;pytest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;ruff&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;requires&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;python_version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3.12&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Pipfile.lock&lt;/code&gt; 은 JSON 포맷이고, 각 패키지의 해시와 정확한 버전이 고정됩니다. 이 파일은 반드시 Git 에 커밋해야 합니다.&lt;/p&gt;
&lt;h2&gt;2026 대체 도구 비교표&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;pipenv&lt;/th&gt;
&lt;th&gt;poetry&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;uv&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;pip-tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;의존성 해결 속도&lt;/td&gt;
&lt;td&gt;매우 느림&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;매우 빠름 (10~100배)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;락파일 정확성&lt;/td&gt;
&lt;td&gt;좋음&lt;/td&gt;
&lt;td&gt;매우 좋음&lt;/td&gt;
&lt;td&gt;매우 좋음&lt;/td&gt;
&lt;td&gt;좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가상환경 관리&lt;/td&gt;
&lt;td&gt;내장&lt;/td&gt;
&lt;td&gt;내장&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;내장 + Python 설치까지&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;별도 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트 메타데이터&lt;/td&gt;
&lt;td&gt;Pipfile (비표준)&lt;/td&gt;
&lt;td&gt;pyproject.toml (PEP 621 부분 호환)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;pyproject.toml (PEP 표준 준수)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;requirements.in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드 / 배포&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커뮤니티 활성도&lt;/td&gt;
&lt;td&gt;정체&lt;/td&gt;
&lt;td&gt;활발&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;폭발적 성장&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습 곡선&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026 추천도&lt;/td&gt;
&lt;td&gt;레거시만&lt;/td&gt;
&lt;td&gt;권장&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;적극 권장&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;미니멀리스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;신규 프로젝트&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;uv&lt;/code&gt; 를 1순위로 고르세요. Rust 로 작성되어 속도가 압도적이고, 파이썬 버전 관리까지 한 번에 해결합니다 (pyenv 가 필요 없어집니다).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빌드/배포까지 깊게 다루는 라이브러리 프로젝트&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;poetry&lt;/code&gt; 도 여전히 훌륭한 선택지입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단순함이 최고다&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;pip + pip-tools + venv&lt;/code&gt; 조합도 충분합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pipenv 는?&lt;/strong&gt; 기존 프로젝트가 이미 쓰고 있을 때만.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마이그레이션 힌트 — Pipfile → pyproject.toml&lt;/h2&gt;
&lt;h3&gt;왜 굳이 옮겨야 하나?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;속도&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;pipenv lock&lt;/code&gt; 이 5 분 걸리던 게 &lt;code class=&quot;language-text&quot;&gt;uv lock&lt;/code&gt; 은 3 초로 줄어듭니다. CI 시간이 체감될 정도로 달라집니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;표준&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 은 PEP 621 기반 파이썬 공식 표준입니다. 도구 이식성이 훨씬 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;생태계&lt;/strong&gt;: 새로운 도구들(ruff, mypy, pytest 설정 등)이 &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 을 1급 시민으로 취급합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;uv 로 옮기는 단계별 체크리스트&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;uv 설치&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# macOS / Linux&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Windows (PowerShell)&lt;/span&gt;
irm https://astral.sh/uv/install.ps1 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; iex&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipfile 내용 백업 (읽기용)&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; Pipfile Pipfile.backup
&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; Pipfile.lock Pipfile.lock.backup&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;uv 프로젝트 초기화&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;uv init --no-readme&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존 디렉터리에서도 안전하게 &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 을 만들어 줍니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;의존성 이관&lt;/strong&gt;
Pipfile 의 &lt;code class=&quot;language-text&quot;&gt;[packages]&lt;/code&gt; 항목을 읽어 하나씩 옮깁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;uv &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; requests fastapi
uv &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--dev&lt;/span&gt; pytest ruff&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;파이썬 버전 지정&lt;/strong&gt;
Pipfile 의 &lt;code class=&quot;language-text&quot;&gt;python_version&lt;/code&gt; 을 &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;requires-python&lt;/code&gt; 으로 옮깁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;toml&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;requires-python&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;=3.12&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;락파일 생성 및 설치 검증&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;uv lock
uv &lt;span class=&quot;token function&quot;&gt;sync&lt;/span&gt;
uv run pytest&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;팀 공지 + CI 파이프라인 수정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions / GitLab CI 의 &lt;code class=&quot;language-text&quot;&gt;pipenv sync&lt;/code&gt; 를 &lt;code class=&quot;language-text&quot;&gt;uv sync --frozen&lt;/code&gt; 으로 교체&lt;/li&gt;
&lt;li&gt;Dockerfile 에서 &lt;code class=&quot;language-text&quot;&gt;pipenv install --deploy&lt;/code&gt; 를 &lt;code class=&quot;language-text&quot;&gt;uv sync --frozen --no-dev&lt;/code&gt; 로 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipfile 제거&lt;/strong&gt;
모두 동작하는 것을 확인한 후 &lt;code class=&quot;language-text&quot;&gt;Pipfile&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Pipfile.lock&lt;/code&gt; 을 삭제하고 커밋합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;poetry 로 옮기고 싶다면&lt;/h3&gt;
&lt;p&gt;거의 동일한 흐름입니다. &lt;code class=&quot;language-text&quot;&gt;poetry init&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;poetry add &amp;lt;패키지&gt;&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;poetry lock&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;poetry install&lt;/code&gt;. pyproject.toml 스키마가 poetry 전용 섹션(&lt;code class=&quot;language-text&quot;&gt;[tool.poetry]&lt;/code&gt;)을 쓰다가 최근에는 PEP 621 표준(&lt;code class=&quot;language-text&quot;&gt;[project]&lt;/code&gt;) 도 지원하니, 신규 프로젝트라면 표준 스키마를 권장합니다.&lt;/p&gt;
&lt;h2&gt;언제 pipenv 를 &quot;유지&quot;하는 게 나은가&lt;/h2&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;/li&gt;
&lt;li&gt;&lt;strong&gt;팀원 전원이 pipenv 에 익숙&lt;/strong&gt;하고, 교육 비용이 마이그레이션 이득보다 클 때.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;레거시 배포 파이프라인&lt;/strong&gt;이 pipenv 에 깊게 묶여 있고, 다음 분기까지 손 댈 여유가 없을 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대신 새로 시작하는 마이크로서비스나 분리 가능한 모듈이 있다면, &lt;strong&gt;그 프로젝트부터 uv 로 시작해 사내 레퍼런스&lt;/strong&gt;를 만들어 두면 좋습니다. 나중에 전사 마이그레이션이 훨씬 수월해집니다.&lt;/p&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. pipenv 진짜 망한 건가요? 내일 당장 못 쓰게 되나요?&lt;/h3&gt;
&lt;p&gt;아니요. 오늘 당장 사라지지는 않습니다. PyPI 에서 계속 설치 가능하고 기본 기능은 작동합니다. 다만 &lt;strong&gt;신기능·성능 개선은 기대하기 어렵고&lt;/strong&gt;, 이슈 해결 속도도 매우 느립니다. &quot;유지는 되지만 성장은 멈춘&quot; 상태라고 보시면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q2. uv 로 갈아타면 구체적으로 뭐가 좋아지나요?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;락 생성 속도&lt;/strong&gt;: 체감상 10~100 배 빨라집니다. 큰 모노레포에서는 차이가 더 극적입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파이썬 버전 관리 통합&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;uv python install 3.12&lt;/code&gt; 한 줄이면 됩니다. pyenv 가 필요 없어집니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;표준 준수&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 중심이라 ruff, mypy, pytest 등과 설정을 한 파일에 모을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단일 바이너리&lt;/strong&gt;: Rust 로 작성되어 의존성이 없어 CI 캐시 관리도 간단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Q3. pip-tools 면 충분하지 않나요?&lt;/h3&gt;
&lt;p&gt;&quot;가상환경은 venv 로, 락파일은 pip-compile 로&quot; 조합은 여전히 유효합니다. 미니멀리스트에게 좋습니다. 다만 &lt;strong&gt;명령어 통합성&lt;/strong&gt;과 &lt;strong&gt;속도&lt;/strong&gt; 면에서 uv 가 더 낫고, uv 는 내부적으로 pip-tools 의 역할까지 흡수했다고 보시면 됩니다.&lt;/p&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/pyenv/&quot;&gt;pyenv 로 파이썬 버전 관리하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/ruff/&quot;&gt;Ruff — 2026 파이썬 린터/포매터 표준&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pipenv.pypa.io/&quot;&gt;pipenv 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;uv 공식 문서 (Astral)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://python-poetry.org/docs/&quot;&gt;Poetry 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0621/&quot;&gt;PEP 621 — pyproject.toml 프로젝트 메타데이터&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;정리해보면, &lt;strong&gt;pipenv 는 &quot;한 시대를 열었던 도구&quot;&lt;/strong&gt; 로 기억될 겁니다. 파이썬 패키지 관리에 락파일과 통합 워크플로라는 개념을 대중화한 공은 분명히 있습니다. 다만 2026 현재, 새 프로젝트를 시작하는 분이라면 저는 망설임 없이 &lt;strong&gt;uv&lt;/strong&gt; 를 권합니다.&lt;/p&gt;
&lt;p&gt;여러분의 팀은 아직 pipenv 를 쓰고 계신가요? 아니면 이미 uv 나 poetry 로 옮기셨나요? 마이그레이션 중 겪은 이슈나, &quot;이런 상황에서는 pipenv 가 여전히 낫다&quot;는 반박이 있다면 댓글로 공유해 주세요. 다음 글에서 &lt;strong&gt;uv 실전 튜토리얼&lt;/strong&gt;로 찾아뵙겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[pyenv 실전 가이드 — Python 버전 관리 (2026, uv 와 비교)]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Python 3.10 으로 돌아가는 레거시 프로젝트 하나, 3.12 로 시작한 신규 프로젝트 하나, 3.1…]]></description><link>https://blog.javapark.kr/pyenv/</link><guid isPermaLink="false">https://blog.javapark.kr/pyenv/</guid><pubDate>Sun, 28 Jan 2024 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Python 3.10 으로 돌아가는 레거시 프로젝트 하나, 3.12 로 시작한 신규 프로젝트 하나, 3.13 이 기본인 실험용 스크립트 하나 — 이걸 한 노트북에서 어떻게 깔끔하게 돌리고 계신가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;2026년 현재, Python 버전 관리의 주도권은 &lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;uv (Astral)&lt;/a&gt; 로 넘어가는 흐름&lt;/strong&gt; 입니다. uv 하나로 &lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;pipenv&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;pip&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;virtualenv&lt;/code&gt; 역할을 거의 대체할 수 있을 만큼 빠르고 촘촘합니다. 그럼에도 &lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; 는 아직 &quot;시스템 전역 Python 인터프리터 스위칭&quot; 이라는 자기 영역에서 확실히 유효하고, 이미 구축해둔 환경을 굳이 갈아엎을 이유는 없습니다.&lt;/p&gt;
&lt;p&gt;이 글은 pyenv 를 &lt;strong&gt;처음 쓰는 분&lt;/strong&gt; 과 &lt;strong&gt;계속 쓸지 고민인 분&lt;/strong&gt; 양쪽을 위한 실전 가이드입니다. 설치·명령어·&lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 같은 기본기부터, 2026 시점의 솔직한 도구 비교, 그리고 uv 로의 마이그레이션 여부까지 한 번에 정리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 Python 버전 관리가 필요한가&lt;/h2&gt;
&lt;p&gt;Python 은 빠른 주기로 새 버전을 내놓습니다. 2025~2026 년에는 이미 3.13 이 안정화 단계이고, 3.14 가 릴리스 사이클을 타고 있죠. 현실 프로젝트는 대략 이런 모습입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;레거시 서비스&lt;/strong&gt;: Python 3.9 또는 3.10 고정. 업그레이드 계획은 있지만 언제가 될지 모름.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;운영 중 서비스&lt;/strong&gt;: Python 3.11 ~ 3.12. 성능 개선 혜택을 누림.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;신규 / 실험 프로젝트&lt;/strong&gt;: Python 3.13 이상. 새 문법과 표준 라이브러리 활용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 분석 / ML&lt;/strong&gt;: 특정 라이브러리 호환성 때문에 마이너 버전까지 고정.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;macOS 에 기본 탑재된 Python, Homebrew 로 깐 Python, 회사 표준 Python — 이런 것들이 PATH 에 뒤엉키면 &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt; 을 한 번 치는 것조차 도박이 됩니다. 이 혼란을 정리해주는 첫 번째 도구가 &lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; 이고, 요즘은 &lt;code class=&quot;language-text&quot;&gt;uv&lt;/code&gt; 가 같은 문제를 더 빠르게 풀고 있다는 것이 이 글의 큰 틀입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;pyenv 란 무엇인가&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; 는 &lt;strong&gt;여러 Python 버전을 한 시스템에 설치하고, 상황에 맞춰 전환해주는 툴&lt;/strong&gt; 입니다.&lt;/p&gt;
&lt;p&gt;핵심 컨셉은 단순합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;~/.pyenv/versions/&lt;/code&gt; 아래에 여러 Python 을 나란히 설치합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; 가 제공하는 &lt;strong&gt;shim (얇은 래퍼 스크립트)&lt;/strong&gt; 이 PATH 에 자리를 잡습니다.&lt;/li&gt;
&lt;li&gt;현재 디렉터리의 &lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 파일, 또는 전역 설정을 읽어 &quot;지금 이 순간의 python 은 무엇&quot; 인지 결정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt; 이라는 명령 하나가 디렉터리마다 다른 실제 인터프리터로 이어지게 만들어 줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치&lt;/h2&gt;
&lt;h3&gt;macOS (Homebrew)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew update
brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; pyenv&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Apple Silicon 에서도 문제없이 동작합니다. 다만 일부 구버전 Python (3.9 이전) 을 컴파일할 때는 &lt;code class=&quot;language-text&quot;&gt;xz&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;openssl&lt;/code&gt; 같은 빌드 의존성을 추가로 요구할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; openssl readline sqlite3 xz zlib tcl-tk&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Linux (pyenv-installer)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; https://pyenv.run &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;우분투/데비안 계열에서는 빌드 도구가 먼저 필요합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; update
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt; build-essential libssl-dev zlib1g-dev &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  libbz2-dev libreadline-dev libsqlite3-dev &lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  libffi-dev liblzma-dev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Windows (pyenv-win)&lt;/h3&gt;
&lt;p&gt;Windows 에서는 원본 &lt;code class=&quot;language-text&quot;&gt;pyenv&lt;/code&gt; 가 동작하지 않고, 포팅 버전인 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv-win&lt;/code&gt;&lt;/strong&gt; 을 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# PowerShell&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;UseBasicParsing `
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token string&quot;&gt;&quot;https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1&quot;&lt;/span&gt; `
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;OutFile &lt;span class=&quot;token string&quot;&gt;&quot;./install-pyenv-win.ps1&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;install-pyenv&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;win&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다만 Windows + Python 조합은 최근 &lt;strong&gt;uv 가 압도적으로 편한 구간&lt;/strong&gt; 입니다. &lt;code class=&quot;language-text&quot;&gt;pyenv-win&lt;/code&gt; 은 유지보수 속도가 원본보다 느리고, 일부 버전 빌드에서 이슈가 있습니다. Windows 사용자라면 이 글을 읽은 뒤 &lt;strong&gt;uv 쪽으로 바로 넘어가는 것도 충분히 합리적&lt;/strong&gt; 인 선택입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;macOS 에서 pyenv 환경 변수 설정 (zshrc)&lt;/h2&gt;
&lt;p&gt;pyenv 는 설치만 한다고 끝이 아니라 &lt;strong&gt;셸 초기화&lt;/strong&gt; 가 반드시 필요합니다. zsh 기준으로 &lt;code class=&quot;language-text&quot;&gt;~/.zshrc&lt;/code&gt; 에 다음을 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;PYENV_ROOT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token environment constant&quot;&gt;$HOME&lt;/span&gt;/.pyenv&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$PYENV_ROOT&lt;/span&gt;/bin&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;&lt;span class=&quot;token environment constant&quot;&gt;PATH&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$PYENV_ROOT&lt;/span&gt;/bin:&lt;span class=&quot;token environment constant&quot;&gt;$PATH&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;eval&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;pyenv init - &lt;span class=&quot;token function&quot;&gt;zsh&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;주의할 점은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;eval &quot;$(pyenv init -)&quot;&lt;/code&gt; 은 반드시 PATH 설정 뒤에&lt;/strong&gt; 와야 합니다. 순서가 바뀌면 shim 이 먹지 않습니다.&lt;/li&gt;
&lt;li&gt;Homebrew 설치 버전을 &lt;code class=&quot;language-text&quot;&gt;pyenv init&lt;/code&gt; 보다 앞에 두면, &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt; 이 Homebrew 쪽으로 새어나갑니다.&lt;/li&gt;
&lt;li&gt;변경 후에는 &lt;code class=&quot;language-text&quot;&gt;source ~/.zshrc&lt;/code&gt; 또는 터미널 재시작이 필수입니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv doctor&lt;/code&gt; 가 있다면 돌려보고, 없다면 &lt;code class=&quot;language-text&quot;&gt;pyenv versions&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;which python&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;python --version&lt;/code&gt; 3종 세트로 검증하세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;bash 사용자라면 &lt;code class=&quot;language-text&quot;&gt;~/.bashrc&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;~/.bash_profile&lt;/code&gt; 에 같은 내용을 넣으면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기본 명령어 6 개만 외우면 됩니다&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 설치 가능한 전체 버전 목록&lt;/span&gt;
pyenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--list&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 특정 버전 설치&lt;/span&gt;
pyenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.12&lt;/span&gt;.7

&lt;span class=&quot;token comment&quot;&gt;# 설치된 버전 확인 (현재 활성 버전에 * 표시)&lt;/span&gt;
pyenv versions

&lt;span class=&quot;token comment&quot;&gt;# 전역 기본 Python 지정&lt;/span&gt;
pyenv global &lt;span class=&quot;token number&quot;&gt;3.12&lt;/span&gt;.7

&lt;span class=&quot;token comment&quot;&gt;# 현재 디렉터리 전용 Python 지정 (.python-version 파일 생성)&lt;/span&gt;
pyenv &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.11&lt;/span&gt;.9

&lt;span class=&quot;token comment&quot;&gt;# 현재 셸에서만 임시로 사용 (터미널 닫으면 해제)&lt;/span&gt;
pyenv shell &lt;span class=&quot;token number&quot;&gt;3.13&lt;/span&gt;.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사실상 이 여섯 개면 일상 업무의 95% 는 처리됩니다. 나머지 &lt;code class=&quot;language-text&quot;&gt;pyenv uninstall&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pyenv rehash&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pyenv which&lt;/code&gt; 정도는 필요할 때 찾아봐도 늦지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 파일 — 팀 프로젝트의 숨은 주인공&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv local 3.11.9&lt;/code&gt; 를 실행하면, 그 디렉터리에 &lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 이라는 평범한 텍스트 파일 하나가 생깁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; .python-version
&lt;span class=&quot;token comment&quot;&gt;# 3.11.9&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 파일을 &lt;strong&gt;Git 에 커밋&lt;/strong&gt; 하는 순간, 같은 리포지토리를 clone 한 팀원은 해당 디렉터리에 들어가기만 해도 자동으로 같은 Python 버전이 적용됩니다 (pyenv 가 설치되어 있다면). 문서로 &quot;우리 프로젝트는 Python 3.11 쓰세요&quot; 라고 적어두는 것보다 훨씬 강제력이 있습니다.&lt;/p&gt;
&lt;p&gt;더 재미있는 점은, &lt;strong&gt;이 파일 포맷이 사실상 표준이 되어 uv, rye, hatch 같은 신흥 툴들도 &lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 을 그대로 읽는다&lt;/strong&gt; 는 것입니다. pyenv 를 떠나더라도 이 습관은 그대로 가져갈 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;pyenv vs venv vs pipenv vs poetry vs uv — 한 표로 정리&lt;/h2&gt;
&lt;p&gt;실무에서 가장 많이 헷갈리는 질문이 &quot;이 도구들 역할이 겹치는 거 같은데 뭐가 뭐죠?&quot; 입니다. 2026 년 기준으로 솔직하게 정리하면 이렇습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;도구&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Python 버전 관리&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;가상환경&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;패키지 관리&lt;/th&gt;
&lt;th align=&quot;center&quot;&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;&lt;strong&gt;pyenv&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;느림 (빌드)&lt;/td&gt;
&lt;td&gt;시스템 Python 스위처, 여전히 단단함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;venv&lt;/strong&gt; (표준)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;빠름&lt;/td&gt;
&lt;td&gt;의존성 없이 가상환경만 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pipenv&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;느림&lt;/td&gt;
&lt;td&gt;한때 표준, 지금은 관성으로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;poetry&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;
&lt;td&gt;라이브러리 배포 프로젝트에 여전히 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;uv&lt;/strong&gt; (Astral)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;매우 빠름&lt;/td&gt;
&lt;td&gt;2026 년의 현실적 default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여기서 한 번 더 강조할 점은, &lt;strong&gt;uv 는 Python 자체 설치까지 담당&lt;/strong&gt; 한다는 것입니다. &lt;code class=&quot;language-text&quot;&gt;uv python install 3.12&lt;/code&gt; 한 줄이면 pyenv 가 해주던 역할을 대체합니다. 프로젝트별 Python 버전 고정은 그대로 &lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 파일을 읽습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2026 년 솔직 평가 — pyenv, 계속 써야 할까?&lt;/h2&gt;
&lt;p&gt;블로그니까 &lt;strong&gt;있는 그대로 써보고 느낀 바&lt;/strong&gt; 를 적겠습니다.&lt;/p&gt;
&lt;h3&gt;이런 분은 pyenv 유지하세요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;이미 pyenv 에 익숙하고 환경이 잘 돌아가고 있다. (굳이 갈아엎지 마세요, 시간 아깝습니다.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시스템 Python 스위칭만&lt;/strong&gt; 원한다. 가상환경/패키지는 각 프로젝트 툴(poetry, uv 등) 에 맡기고 싶다.&lt;/li&gt;
&lt;li&gt;회사 표준 툴체인이 pyenv 기반이다.&lt;/li&gt;
&lt;li&gt;Linux 서버에서 가볍게 Python 여러 개만 깔아두고 싶다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;이런 분은 uv 로 넘어가세요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;지금 새 프로젝트를 시작하는&lt;/strong&gt; 중이다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;pip install&lt;/code&gt; 의 느린 속도, 깨지는 lockfile, pipenv 의 멈춤 현상에 지쳤다.&lt;/li&gt;
&lt;li&gt;Windows 환경이고 pyenv-win 의 한계를 경험했다.&lt;/li&gt;
&lt;li&gt;팀 전체의 빌드 시간을 줄이고 싶다 (uv 는 실제로 10~100배 빠릅니다).&lt;/li&gt;
&lt;li&gt;Python 설치·가상환경·패키지 관리까지 &lt;strong&gt;한 도구로&lt;/strong&gt; 끝내고 싶다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심은 간단합니다. &lt;strong&gt;pyenv 가 &quot;틀렸다&quot; 가 아니라, uv 가 &quot;같은 일을 더 빠르고 넓게&quot; 해준다&lt;/strong&gt; 는 것입니다. 새로 시작한다면 uv 를 먼저 고려하는 것이 2026 년의 합리적 선택입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;자주 쓰이는 팁 몇 가지&lt;/h2&gt;
&lt;h3&gt;1. 설치 속도 높이기 (macOS)&lt;/h3&gt;
&lt;p&gt;Python 빌드는 생각보다 느립니다. 옵션으로 최적화 빌드를 꺼서 속도를 올릴 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;PYTHON_CONFIGURE_OPTS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; pyenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.12&lt;/span&gt;.7&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;반대로 성능이 중요한 서버라면 &lt;code class=&quot;language-text&quot;&gt;--enable-optimizations&lt;/code&gt; 를 켜서 오래 걸리더라도 최적화 빌드를 선택하세요.&lt;/p&gt;
&lt;h3&gt;2. Shim 이 꼬였을 때&lt;/h3&gt;
&lt;p&gt;새 버전을 설치했는데 &lt;code class=&quot;language-text&quot;&gt;python --version&lt;/code&gt; 에 반영이 안 되면, shim 을 새로고침합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pyenv rehash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. &lt;code class=&quot;language-text&quot;&gt;pyenv-virtualenv&lt;/code&gt; 는 굳이?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pyenv-virtualenv&lt;/code&gt; 는 pyenv 로 설치한 각 Python 에 독립 가상환경을 연결해주는 플러그인입니다. 유용하긴 하지만, 가상환경은 표준 &lt;code class=&quot;language-text&quot;&gt;venv&lt;/code&gt; 로도 충분하고, 프로젝트 단위 관리는 uv/poetry 에 맡기는 편이 구조가 깔끔합니다. 신규 도입은 권장하지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. pyenv 에서 uv 로 어떻게 마이그레이션하나요?&lt;/h3&gt;
&lt;p&gt;세 줄이면 끝납니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 1. uv 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 2. 프로젝트 디렉터리에서 같은 Python 버전 확보 (.python-version 그대로 인식)&lt;/span&gt;
uv python &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 3. 가상환경 + 의존성 싱크&lt;/span&gt;
uv &lt;span class=&quot;token function&quot;&gt;sync&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존 &lt;code class=&quot;language-text&quot;&gt;.python-version&lt;/code&gt; 파일, &lt;code class=&quot;language-text&quot;&gt;requirements.txt&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; 은 대부분 그대로 사용할 수 있습니다. pyenv 를 바로 삭제할 필요도 없습니다. 한동안 둘을 함께 쓰다가, 손이 완전히 uv 로 넘어갔을 때 조용히 &lt;code class=&quot;language-text&quot;&gt;brew uninstall pyenv&lt;/code&gt; 해주면 됩니다.&lt;/p&gt;
&lt;h3&gt;Q2. Windows 에서 pyenv 써도 되나요?&lt;/h3&gt;
&lt;p&gt;가능하긴 하지만 &lt;strong&gt;강하게 권하지는 않습니다&lt;/strong&gt;. &lt;code class=&quot;language-text&quot;&gt;pyenv-win&lt;/code&gt; 은 원본과 별개 프로젝트로 유지되며, 버전 대응이 느리고 빌드 이슈도 가끔 있습니다. Windows 사용자라면 &lt;code class=&quot;language-text&quot;&gt;uv&lt;/code&gt; 또는 python.org 공식 인스톨러 + &lt;code class=&quot;language-text&quot;&gt;uv&lt;/code&gt; 조합이 훨씬 매끄럽습니다.&lt;/p&gt;
&lt;h3&gt;Q3. pyenv 로 설치한 Python 이 macOS 업데이트 후 안 잡혀요.&lt;/h3&gt;
&lt;p&gt;macOS 대형 업데이트 직후 자주 겪는 문제입니다. Xcode Command Line Tools 를 다시 설치하고, 기존에 빌드해둔 Python 을 재설치하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xcode-select &lt;span class=&quot;token parameter variable&quot;&gt;--install&lt;/span&gt;
pyenv uninstall &lt;span class=&quot;token number&quot;&gt;3.12&lt;/span&gt;.7
pyenv &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.12&lt;/span&gt;.7&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그래도 해결되지 않으면 &lt;code class=&quot;language-text&quot;&gt;~/.zshrc&lt;/code&gt; 의 pyenv 초기화 블록이 Homebrew PATH 뒤로 밀려있지 않은지 확인하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/pipenv/&quot;&gt;파이썬 프로젝트 관리의 혁신: pipenv 사용가이드&lt;/a&gt; — 패키지 / 가상환경 관리 툴, pyenv 와 함께 쓰던 고전 조합.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/ruff/&quot;&gt;Ruff 를 이용한 python 코드 품질 관리&lt;/a&gt; — 같은 Astral 팀이 만든, uv 와 철학을 공유하는 초고속 린터.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pyenv/pyenv&quot;&gt;pyenv — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pyenv-win/pyenv-win&quot;&gt;pyenv-win — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;uv Documentation — Astral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0602/&quot;&gt;Python 공식 릴리스 스케줄&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Python 버전 관리는 &quot;내 개발 환경의 청결도&quot; 를 결정짓는 가장 기초 공사입니다. pyenv 를 쓰든 uv 로 넘어가든, 중요한 건 &lt;strong&gt;&quot;내 &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt; 이 지금 무엇을 가리키는지 한 줄로 설명할 수 있어야 한다&quot;&lt;/strong&gt; 는 원칙입니다.&lt;/p&gt;
&lt;p&gt;여러분은 pyenv / uv / 기타 중 어느 조합을 쓰고 계신가요? 마이그레이션하면서 겪은 시행착오나, 아직 pyenv 를 유지하는 나름의 이유가 있다면 댓글로 공유해 주세요. 다음 글에서는 같은 주제를 &lt;strong&gt;uv 단독 워크플로우&lt;/strong&gt; 관점에서 더 깊이 풀어볼 예정입니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Git 실전 완벽 가이드 — 현대적 명령어로 배우는 버전 관리 기본]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "git 명령어, 매번 검색하고 계신가요?" 결론부터 말씀드리면 — Git의 핵심 개념은 10년 전과 달라지지 않았지만, 현대적 명령어(, )로 정리하면 훨씬 직관적입니다. 이 글 하나만 북마크해두면 일상 작업의 9…]]></description><link>https://blog.javapark.kr/git/</link><guid isPermaLink="false">https://blog.javapark.kr/git/</guid><pubDate>Wed, 22 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;git 명령어, 매번 검색하고 계신가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Git의 핵심 개념은 10년 전과 달라지지 않았지만, 현대적 명령어(&lt;code class=&quot;language-text&quot;&gt;git switch&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git restore&lt;/code&gt;)로 정리하면 훨씬 직관적입니다.&lt;/strong&gt; 이 글 하나만 북마크해두면 일상 작업의 95%는 커버됩니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 Git 2.45+ 기준으로 기본 개념, 필수 명령어, 실전 시나리오, 커밋 컨벤션까지 한 번에 정리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;형상관리란 무엇인가&lt;/h2&gt;
&lt;p&gt;형상관리(SCM, Source Code Management)는 &lt;strong&gt;소스 코드의 변경 이력을 체계적으로 관리하는 것&lt;/strong&gt;입니다. 누가, 언제, 왜, 무엇을 바꿨는지를 기록합니다.&lt;/p&gt;
&lt;h3&gt;형상관리 없이 개발하면&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;프로젝트_최종.zip&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;프로젝트_최종_진짜최종.zip&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;프로젝트_최종_v3_수정.zip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&quot;어제 고친 코드가 뭐였지?&quot; — 기억에 의존&lt;/li&gt;
&lt;li&gt;&quot;이 버그, 언제부터 있었지?&quot; — 추적 불가&lt;/li&gt;
&lt;li&gt;컴퓨터 포맷하면 모든 히스토리 소실&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2026년 현재, Git은 사실상 유일한 선택지&lt;/h3&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;&lt;strong&gt;Git&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;분산형&lt;/td&gt;
&lt;td&gt;업계 표준, 압도적 1위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subversion (SVN)&lt;/td&gt;
&lt;td&gt;중앙집중형&lt;/td&gt;
&lt;td&gt;레거시 유지보수 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mercurial&lt;/td&gt;
&lt;td&gt;분산형&lt;/td&gt;
&lt;td&gt;거의 사용 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GitHub, GitLab, Bitbucket 모두 Git 기반입니다. 다른 도구를 익힐 이유가 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Git의 4가지 영역 — 이것만 이해하면 끝&lt;/h2&gt;
&lt;p&gt;Git을 이해하는 가장 빠른 길은 &lt;strong&gt;4가지 영역&lt;/strong&gt;을 머리에 그리는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────────────┐   git add    ┌──────────────┐  git commit  ┌───────────────┐  git push  ┌───────────────┐
│ Working Directory│ ───────────&gt; │   Staging    │ ───────────&gt; │ Local Repo    │ ────────&gt;  │ Remote Repo   │
│ (작업 디렉토리) │              │  (스테이지)  │              │ (로컬 저장소) │            │ (원격 저장소) │
└─────────────────┘              └──────────────┘              └───────────────┘            └───────────────┘
       ▲                                                                                              │
       └──────────────────────────────── git pull / git fetch ◀───────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;Working Directory&lt;/td&gt;
&lt;td&gt;실제 편집 중인 파일&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git status&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git diff&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging Area&lt;/td&gt;
&lt;td&gt;다음 커밋에 포함할 변경 사항&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git add&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git restore --staged&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Repository&lt;/td&gt;
&lt;td&gt;내 컴퓨터의 커밋 히스토리&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git commit&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote Repository&lt;/td&gt;
&lt;td&gt;GitHub 등 원격 저장소&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git push&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git pull&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 흐름만 머리에 있으면 대부분의 명령어가 자연스럽게 이해됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;필수 명령어 레퍼런스 (2026 현대 명령어 기준)&lt;/h2&gt;
&lt;p&gt;2019년 Git 2.23에서 도입된 &lt;code class=&quot;language-text&quot;&gt;git switch&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;git restore&lt;/code&gt;가 현재 완전히 정착했습니다. &lt;code class=&quot;language-text&quot;&gt;git checkout&lt;/code&gt;은 여전히 동작하지만, &lt;strong&gt;브랜치 이동과 파일 복원을 한 명령어가 같이 처리해서 혼란스러워&lt;/strong&gt; 권장되지 않습니다. 새로 시작하시는 분은 &lt;code class=&quot;language-text&quot;&gt;switch&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;restore&lt;/code&gt;로 익히세요.&lt;/p&gt;
&lt;h3&gt;초기화 · 복제&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 디렉토리를 Git 저장소로 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git clone &amp;lt;url&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격 저장소를 로컬로 복제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git clone --depth=1 &amp;lt;url&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;최신 커밋 1개만 가져오기 (얕은 복제)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;상태 확인&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;변경/스테이징 상태 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git status -s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;짧은 형식으로 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git diff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Working ↔ Staging 차이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git diff --staged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Staging ↔ Local Repo 차이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git log --oneline --graph --all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;그래프 형태로 전체 히스토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git log -p &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;특정 파일의 변경 이력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git blame &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;각 줄의 마지막 수정자 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;변경 추적 · 커밋&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git add &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;특정 파일을 스테이징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git add .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 디렉토리 변경 전체 스테이징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git add -p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;변경 단위(hunk)별로 선택 스테이징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git commit -m &quot;메시지&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스테이지된 변경을 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git commit --amend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;마지막 커밋 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;브랜치 — &lt;code class=&quot;language-text&quot;&gt;git switch&lt;/code&gt; (현대 명령어)&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;로컬 브랜치 목록&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git branch -a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격 포함 전체 브랜치&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git switch &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;브랜치 이동&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git checkout &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git switch -c &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;브랜치 생성 + 이동&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git checkout -b &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git switch -&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;직전 브랜치로 이동&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git checkout -&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git branch -d &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;브랜치 삭제 (병합된 것만)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git branch -D &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;강제 삭제&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git branch -m &amp;lt;새이름&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 브랜치 이름 변경&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;파일 복원 — &lt;code class=&quot;language-text&quot;&gt;git restore&lt;/code&gt; (현대 명령어)&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git restore &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Working Directory 변경 취소&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git checkout -- &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git restore --staged &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스테이징 취소 (변경은 유지)&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git reset HEAD &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git restore --source=HEAD~2 &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;특정 커밋 시점으로 파일 복원&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git checkout HEAD~2 -- &amp;lt;파일&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;원격 저장소&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git remote -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격 저장소 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git remote add origin &amp;lt;url&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격 연결 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격 변경 가져오기 (병합 안 함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git pull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;merge&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git pull --rebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;rebase&lt;/code&gt; (히스토리 깔끔)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원격에 푸시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git push -u origin &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;업스트림 설정하며 푸시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;병합 · 리베이스&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git merge &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 브랜치로 병합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git merge --no-ff &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fast-forward 방지 (머지 커밋 생성)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git rebase &amp;lt;브랜치&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 브랜치를 다른 브랜치 위로 재배치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git rebase -i HEAD~3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;최근 3개 커밋 대화형 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git cherry-pick &amp;lt;커밋해시&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;특정 커밋만 가져오기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;태그 · Stash&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;git tag v1.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 커밋에 태그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git tag -a v1.0.0 -m &quot;릴리즈&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;주석 포함 태그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git push origin v1.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;태그 푸시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git stash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 중 변경 임시 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git stash pop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;가장 최근 stash 복원 후 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git stash list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;stash 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;git stash drop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;가장 최근 stash 버리기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;실전 시나리오 — 이럴 땐 이렇게&lt;/h2&gt;
&lt;h3&gt;시나리오 1. 실수 커밋 되돌리기 — &lt;code class=&quot;language-text&quot;&gt;reset&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;revert&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;두 명령어는 목적이 다릅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# reset: 히스토리를 &quot;없었던 일&quot;로 만듭니다 (로컬 전용)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--soft&lt;/span&gt; HEAD~1   &lt;span class=&quot;token comment&quot;&gt;# 커밋만 취소, 변경은 Staging에 유지&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--mixed&lt;/span&gt; HEAD~1  &lt;span class=&quot;token comment&quot;&gt;# 커밋+Staging 취소 (기본값, 변경은 유지)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; HEAD~1   &lt;span class=&quot;token comment&quot;&gt;# 커밋+Staging+변경 전부 삭제 (주의!)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# revert: &quot;취소하는 새 커밋&quot;을 추가합니다 (공유 브랜치에서 안전)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; revert HEAD           &lt;span class=&quot;token comment&quot;&gt;# 마지막 커밋의 반대 커밋 생성&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;기준&lt;/strong&gt;: 아직 푸시 안 했으면 &lt;code class=&quot;language-text&quot;&gt;reset&lt;/code&gt;, 이미 원격에 올라갔으면 &lt;strong&gt;반드시&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;revert&lt;/code&gt;를 쓰세요. 공유 브랜치를 &lt;code class=&quot;language-text&quot;&gt;reset --hard&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;push --force&lt;/code&gt;하면 팀원들의 히스토리가 깨집니다.&lt;/p&gt;
&lt;h3&gt;시나리오 2. 커밋 메시지 수정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 마지막 커밋 (아직 푸시 전)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;--amend&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: 올바른 메시지&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 이미 푸시했다면 — 강제 푸시 필요 (팀에게 공지 후!)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;--amend&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;수정된 메시지&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push --force-with-lease&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;--force-with-lease&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;--force&lt;/code&gt;보다 안전합니다. 내가 모르는 새 커밋이 원격에 있으면 푸시를 거부합니다.&lt;/p&gt;
&lt;h3&gt;시나리오 3. 마지막 커밋에 파일 추가&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 빠뜨린 파일 스테이징&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; src/forgotten.js

&lt;span class=&quot;token comment&quot;&gt;# --no-edit: 커밋 메시지 유지한 채 추가&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;--amend&lt;/span&gt; --no-edit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;시나리오 4. 브랜치 병합 충돌 해결&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; switch main
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; switch feature/login
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; merge main
&lt;span class=&quot;token comment&quot;&gt;# → 충돌 발생&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 1. 충돌 파일 열면 &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;, =======, &gt;&gt;&gt;&gt;&gt;&gt;&gt; 마커가 보임&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 2. 직접 편집하여 최종 코드 결정&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 3. 해결된 파일 스테이징&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; src/auth.js

&lt;span class=&quot;token comment&quot;&gt;# 4. 병합 커밋 완료&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit

&lt;span class=&quot;token comment&quot;&gt;# 취소하고 싶다면&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; merge &lt;span class=&quot;token parameter variable&quot;&gt;--abort&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;VS Code, IntelliJ의 3-way merge 뷰를 쓰면 훨씬 수월합니다.&lt;/p&gt;
&lt;h3&gt;시나리오 5. 특정 파일만 되돌리기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Working Directory 변경 취소&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore src/config.js

&lt;span class=&quot;token comment&quot;&gt;# Staging만 취소 (변경은 유지)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore &lt;span class=&quot;token parameter variable&quot;&gt;--staged&lt;/span&gt; src/config.js

&lt;span class=&quot;token comment&quot;&gt;# 3개 커밋 전 상태로 파일 복원&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore &lt;span class=&quot;token parameter variable&quot;&gt;--source&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;HEAD~3 src/config.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;시나리오 6. 작업 중 급하게 다른 브랜치로 이동&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 현재 작업 임시 저장&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; stash push &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;프로필 페이지 작업 중&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 긴급 브랜치 이동&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; switch hotfix/urgent
&lt;span class=&quot;token comment&quot;&gt;# ...핫픽스 작업...&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; switch -

&lt;span class=&quot;token comment&quot;&gt;# 작업 복원&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; stash pop&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;.gitignore — 불필요한 파일 제외하기&lt;/h2&gt;
&lt;p&gt;프로젝트에 포함하면 안 되는 파일을 &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt;에 등록합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;gitignore&quot;&gt;&lt;pre class=&quot;language-gitignore&quot;&gt;&lt;code class=&quot;language-gitignore&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 의존성&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;node_modules&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;__pycache__&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;vendor&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 환경 변수 (비밀 키!)&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.env&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.env.local&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.env.&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.local&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 빌드 결과물&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;dist&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;build&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.next&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.cache&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# OS 파일&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.DS_Store&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;Thumbs.db&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# IDE 설정&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.vscode&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;.idea&lt;span class=&quot;token punctuation&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.swp&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 로그&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;.log&lt;/span&gt;
&lt;span class=&quot;token entry string&quot;&gt;npm-debug.log&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;주의&lt;/strong&gt;: &lt;code class=&quot;language-text&quot;&gt;.env&lt;/code&gt; 파일은 &lt;strong&gt;절대 커밋하면 안 됩니다.&lt;/strong&gt; API 키나 비밀번호가 GitHub에 올라가면 봇이 수 분 내에 스캔해서 악용합니다. 실수로 커밋했다면 즉시 키를 &lt;strong&gt;폐기&lt;/strong&gt;하고 재발급하세요. &lt;code class=&quot;language-text&quot;&gt;git rm&lt;/code&gt;으로 지워도 히스토리에는 남습니다.&lt;/p&gt;
&lt;p&gt;이미 추적 중인 파일을 &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt;에 넣어도 무시되지 않습니다. 이럴 땐 &lt;code class=&quot;language-text&quot;&gt;git rm --cached &amp;lt;파일&gt;&lt;/code&gt;로 추적 해제하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;커밋 메시지 컨벤션 — Conventional Commits&lt;/h2&gt;
&lt;p&gt;나쁜 커밋 메시지:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;수정
fix
ㅋㅋ
asdf&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;좋은 커밋 메시지 (Conventional Commits):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;feat: 소셜 로그인 기능 추가
fix: 비밀번호 유효성 검사 오류 수정
docs: README에 설치 가이드 추가
refactor: 인증 미들웨어 구조 개선&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;타입 레퍼런스&lt;/h3&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;&lt;code class=&quot;language-text&quot;&gt;feat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;새 기능&lt;/td&gt;
&lt;td&gt;feat: 검색 필터 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;버그 수정&lt;/td&gt;
&lt;td&gt;fix: 날짜 포맷 오류 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;문서 변경&lt;/td&gt;
&lt;td&gt;docs: API 문서 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;style&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;포맷, 세미콜론 등 (동작 변경 없음)&lt;/td&gt;
&lt;td&gt;style: 들여쓰기 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;refactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;리팩토링&lt;/td&gt;
&lt;td&gt;refactor: 유저 서비스 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;perf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;성능 개선&lt;/td&gt;
&lt;td&gt;perf: 쿼리 N+1 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트 추가/수정&lt;/td&gt;
&lt;td&gt;test: 로그인 단위 테스트 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;chore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빌드, 설정 변경&lt;/td&gt;
&lt;td&gt;chore: eslint 설정 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ci&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI 설정&lt;/td&gt;
&lt;td&gt;ci: GitHub Actions 워크플로우 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;본문까지 작성한 좋은 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;feat(auth): OAuth 2.0 기반 구글 로그인 지원

- 기존 이메일/비밀번호 로그인과 병행 동작
- 최초 로그인 시 프로필 자동 생성
- refresh token은 httpOnly 쿠키에 저장

Closes #142&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 규칙을 따르면 &lt;code class=&quot;language-text&quot;&gt;git log&lt;/code&gt;만 봐도 프로젝트 히스토리가 한눈에 들어오고, 자동 릴리즈 노트 생성(semantic-release 등) 도구와도 연동됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;브랜치 전략 — 팀 규모에 맞게&lt;/h2&gt;
&lt;h3&gt;혼자 개발 (1인)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;main ──●──●──●──●──●&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; 하나로 충분합니다. 실험적인 작업만 &lt;code class=&quot;language-text&quot;&gt;feature/&lt;/code&gt;로 분리하세요.&lt;/p&gt;
&lt;h3&gt;소규모 팀 (2~5명) — GitHub Flow&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;main    ──●────────●────────●──
             \      /  \      /
feature/a  ──●──●──    ──●──●──&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;feature/기능명&lt;/code&gt; 브랜치 생성&lt;/li&gt;
&lt;li&gt;작업 후 Pull Request 생성&lt;/li&gt;
&lt;li&gt;코드 리뷰 후 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에 병합, 브랜치 삭제&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;단순하고 빠릅니다. 웹 서비스, SaaS에 잘 맞습니다.&lt;/p&gt;
&lt;h3&gt;릴리즈 주기가 있는 팀 — Git Flow (축소판)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;main    ──●──────────────●──
           \            /
develop  ──●──●──●──●──●──
              \  /
feature   ──●──●&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;(배포), &lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt;(통합), &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt;(기능), &lt;code class=&quot;language-text&quot;&gt;hotfix&lt;/code&gt;(긴급 패치) 정도로 가볍게 운영하는 게 요즘 트렌드입니다. 전통적인 Git Flow 전체를 엄격히 지키는 팀은 줄어드는 추세입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 시대의 Git 운영 팁&lt;/h2&gt;
&lt;p&gt;Claude Code, Cursor 같은 AI 코딩 도구가 코드를 대량 생성하는 지금, Git의 역할은 더 커졌습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI가 생성한 코드도 의미 단위로 커밋하세요.&lt;/strong&gt; &quot;AI가 만든 것&quot;이라고 한 덩어리로 커밋하면 추적이 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 작업 전 브랜치를 만드세요.&lt;/strong&gt; 기대와 다른 결과가 나오면 브랜치를 통째로 버리면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;공저자 표기를 남기세요.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: 대시보드 차트 구현

Co-Authored-By: Claude &amp;lt;noreply@anthropic.com&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. &lt;code class=&quot;language-text&quot;&gt;git pull&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;의 차이는 뭔가요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;는 원격의 변경을 &lt;strong&gt;가져오기만&lt;/strong&gt; 합니다. 내 로컬 브랜치는 그대로입니다. &lt;code class=&quot;language-text&quot;&gt;git pull&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;git merge&lt;/code&gt;를 한 번에 실행합니다. 충돌이 걱정되거나 원격 상황을 먼저 살펴보고 싶다면 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; 후 &lt;code class=&quot;language-text&quot;&gt;git log origin/main..HEAD&lt;/code&gt;로 확인하는 습관을 들이세요.&lt;/p&gt;
&lt;h3&gt;Q2. &lt;code class=&quot;language-text&quot;&gt;rebase&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;merge&lt;/code&gt;는 언제 어떤 걸 쓰나요?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;공유 브랜치(main, develop)에 합칠 때는 &lt;code class=&quot;language-text&quot;&gt;merge&lt;/code&gt;&lt;/strong&gt;, **내 기능 브랜치를 최신 main으로 따라갈 때는 &lt;code class=&quot;language-text&quot;&gt;rebase&lt;/code&gt;**가 일반적인 기준입니다. &lt;code class=&quot;language-text&quot;&gt;rebase&lt;/code&gt;는 히스토리를 일직선으로 만들어 깔끔하지만, 이미 푸시된 커밋을 리베이스하면 팀원의 히스토리를 망칠 수 있으니 &lt;strong&gt;공유된 커밋엔 절대 금지&lt;/strong&gt;입니다. 요약: &quot;내 브랜치 정리는 rebase, 합치는 건 merge.&quot;&lt;/p&gt;
&lt;h3&gt;Q3. 실수로 &lt;code class=&quot;language-text&quot;&gt;.env&lt;/code&gt;를 커밋했는데 어떻게 지우나요?&lt;/h3&gt;
&lt;p&gt;먼저 &lt;strong&gt;해당 키/비밀번호를 즉시 폐기하고 재발급&lt;/strong&gt;하세요. 이미 GitHub에 올라간 순간 유출된 것으로 간주해야 합니다. 그 다음 &lt;code class=&quot;language-text&quot;&gt;git filter-repo&lt;/code&gt;(권장) 또는 GitHub의 Push Protection/Secret Scanning을 활용해 히스토리에서 제거합니다. &lt;code class=&quot;language-text&quot;&gt;git rm --cached .env&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt; 추가는 &lt;strong&gt;미래 커밋&lt;/strong&gt;에만 영향을 주므로 충분하지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/doc&quot;&gt;Git 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/ko/v2&quot;&gt;Pro Git Book (한국어)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conventionalcommits.org/ko/&quot;&gt;Conventional Commits 스펙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://skills.github.com/&quot;&gt;GitHub Skills — Git 학습 코스&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-switch&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;git switch&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;git restore&lt;/code&gt; 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;여러분은 &lt;code class=&quot;language-text&quot;&gt;git checkout&lt;/code&gt;을 아직 쓰고 계신가요, 아니면 &lt;code class=&quot;language-text&quot;&gt;git switch&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;restore&lt;/code&gt;로 넘어오셨나요? 나만의 Git 워크플로우나 자주 쓰는 alias가 있다면 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SheetJS 실전 가이드 — 브라우저에서 엑셀 읽고 쓰기 (xlsx vs ExcelJS 비교)]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "관리자 페이지에서 엑셀 다운로드 좀 붙여주세요."
"고객사에서 엑셀 업로드로 일괄 등록하고 싶다는데요…" 프론트엔드 개발을 오래 하신 분이라면 이 문장은 거의 분기별로 한 번씩 듣게 됩니다. 서버에서 CSV…]]></description><link>https://blog.javapark.kr/sheetjs/</link><guid isPermaLink="false">https://blog.javapark.kr/sheetjs/</guid><pubDate>Thu, 09 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;관리자 페이지에서 엑셀 다운로드 좀 붙여주세요.&quot;
&quot;고객사에서 엑셀 업로드로 일괄 등록하고 싶다는데요…&quot;&lt;/p&gt;
&lt;p&gt;프론트엔드 개발을 오래 하신 분이라면 이 문장은 거의 분기별로 한 번씩 듣게 됩니다. 서버에서 CSV로 내려주면 한글이 깨지고, POI로 만들면 배포 부담이 커지고 — 결국 다시 &lt;strong&gt;브라우저에서 직접 엑셀을 만들자&lt;/strong&gt;는 결론에 도달하죠.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;단순 읽기/쓰기는 SheetJS(xlsx) CE&lt;/strong&gt;면 충분하고, &lt;strong&gt;셀 서식·컬럼 폭·이미지까지 건드려야 한다면 ExcelJS&lt;/strong&gt;가 더 편합니다. 이 글에서는 두 라이브러리의 실전 패턴과 선택 기준을 2026년 시점에서 정리합니다.&lt;/p&gt;
&lt;h2&gt;왜 아직도 SheetJS 인가&lt;/h2&gt;
&lt;p&gt;2026년 현재 브라우저 기반 엑셀 라이브러리 생태계에서 사실상 표준은 &lt;strong&gt;SheetJS (xlsx)&lt;/strong&gt; 입니다. 이유는 세 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;포맷 커버리지가 압도적&lt;/strong&gt; — XLSX, XLSM, XLSB, XLS, ODS, CSV, HTML 등 대부분의 스프레드시트 포맷을 한 API로 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존성이 없다&lt;/strong&gt; — 번들 크기는 작지 않지만(약 600KB gzipped), 런타임 의존이 0이라 Webpack/Vite/esbuild 어디에 넣어도 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CE 버전은 Apache 2.0&lt;/strong&gt; — 상업 프로젝트에서도 부담 없이 쓸 수 있습니다. Pro 버전은 스타일링/차트/서식 기능이 추가되지만, 대부분의 관리자 페이지는 CE로 충분합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;단, 한 가지 유의점이 있습니다. &lt;code class=&quot;language-text&quot;&gt;xlsx&lt;/code&gt;는 2023년부터 &lt;strong&gt;npm 공식 레지스트리에서 호스팅되지 않고 SheetJS 자체 CDN을 사용&lt;/strong&gt;하도록 바뀌었습니다. 설치 방식이 예전 포스트들과 다르니 아래 최신 방식을 쓰세요.&lt;/p&gt;
&lt;h2&gt;설치 — 2026년 최신 방식&lt;/h2&gt;
&lt;h3&gt;CDN (가장 빠른 시작)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예전 cdnjs 경로(&lt;code class=&quot;language-text&quot;&gt;cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.0/...&lt;/code&gt;)는 업데이트가 멈춰 있으므로 &lt;strong&gt;공식 CDN을 사용&lt;/strong&gt;하는 것을 권장합니다.&lt;/p&gt;
&lt;h3&gt;npm&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# SheetJS 공식 CDN 레지스트리를 통해 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--save&lt;/span&gt; https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;xlsx&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;ExcelJS (대안)&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; exceljs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ExcelJS &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;exceljs&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ExcelJS는 MIT 라이선스, 순수 JS, npm 공식 레지스트리에 정상 배포되어 있습니다.&lt;/p&gt;
&lt;h2&gt;읽기 — 업로드한 엑셀을 JSON으로&lt;/h2&gt;
&lt;p&gt;브라우저에서 사용자가 선택한 엑셀 파일을 JSON 배열로 바꾸는 가장 기본 패턴입니다. 예전에는 &lt;code class=&quot;language-text&quot;&gt;readAsBinaryString&lt;/code&gt;을 많이 썼지만 &lt;strong&gt;deprecated&lt;/strong&gt; 되었고, 2026년 기준 권장은 &lt;code class=&quot;language-text&quot;&gt;readAsArrayBuffer&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file-input&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;.xlsx,.xls,.csv&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;pre&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;pre&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;file-input&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;change&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ArrayBuffer → Uint8Array&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; workbook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;array&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sheetName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workbook&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SheetNames&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sheet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workbook&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sheets&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sheetName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 헤더를 키로 쓰는 객체 배열&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sheet_to_json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sheet&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;defval&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      rows&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;포인트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;{ defval: &quot;&quot; }&lt;/code&gt; 를 주면 빈 셀이 &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;가 아니라 빈 문자열로 들어와 서버 전송 시 편합니다.&lt;/li&gt;
&lt;li&gt;헤더를 직접 지정하고 싶다면 &lt;code class=&quot;language-text&quot;&gt;{ header: [&quot;id&quot;, &quot;name&quot;, &quot;email&quot;] }&lt;/code&gt; 처럼 넘길 수 있습니다.&lt;/li&gt;
&lt;li&gt;CSV는 한글 인코딩이 꼬이는 경우가 많은데, SheetJS는 BOM과 EUC-KR을 자동 감지하므로 대부분 그대로 읽힙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;쓰기 — JSON을 엑셀로 내려받기&lt;/h2&gt;
&lt;p&gt;반대 방향, 관리자 화면에서 조회한 데이터를 그대로 엑셀로 내려주는 패턴입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;exportToExcel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;export.xlsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 1) 워크북 생성&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; workbook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;book_new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 2) JSON → 시트&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sheet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json_to_sheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 3) 시트 등록&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;book_append_sheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workbook&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sheet&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Sheet1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 4) 파일 다운로드 트리거&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workbook&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;compression&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 사용 예&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;exportToExcel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;홍길동&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hong@example.com&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;김자바&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;java@example.com&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;compression: true&lt;/code&gt;를 주면 XLSX 내부 zip을 압축해 &lt;strong&gt;파일 크기가 30~60% 정도 줄어듭니다&lt;/strong&gt;. 수천 행 이상 내려줄 땐 거의 필수입니다.&lt;/p&gt;
&lt;h3&gt;aoa_to_sheet — 표 레이아웃을 직접 구성&lt;/h3&gt;
&lt;p&gt;JSON이 아니라 &quot;N행 M열&quot;의 2차원 배열로 넘기면 헤더 병합·빈 행 삽입 같은 레이아웃을 자유롭게 짤 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; aoa &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ID&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이름&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이메일&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;홍길동&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hong@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;김자바&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;java@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sheet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aoa_to_sheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aoa&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;컬럼 폭·헤더 스타일 팁&lt;/h2&gt;
&lt;p&gt;CE 버전은 &lt;strong&gt;셀 서식(폰트, 색, 굵기)은 지원하지 않습니다&lt;/strong&gt;. 하지만 &lt;strong&gt;컬럼 폭과 병합 정도는 CE에서도 가능&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 컬럼 폭(글자 수 기준)&lt;/span&gt;
sheet&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!cols&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 헤더 행 고정(첫 행)&lt;/span&gt;
sheet&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!freeze&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;xSplit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;ySplit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 셀 병합: A1:C1&lt;/span&gt;
sheet&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!merges&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;헤더를 &lt;strong&gt;굵게/배경색&lt;/strong&gt;으로 강조하고 싶다면 여기서부터는 ExcelJS 영역입니다.&lt;/p&gt;
&lt;h2&gt;ExcelJS 로 헤더 스타일까지&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ExcelJS &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;exceljs&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;exportStyled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExcelJS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Workbook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ws &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addWorksheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;회원&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  ws&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;columns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ID&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이름&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;이메일&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  ws&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addRows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 헤더 스타일&lt;/span&gt;
  ws&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eachCell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;cell&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;font &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;argb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FFFFFFFF&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    cell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fill &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pattern&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;solid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;fgColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;argb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FF2F5597&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    cell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alignment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;vertical&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;middle&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;horizontal&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; wb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xlsx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url
  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;members.xlsx&quot;&lt;/span&gt;
  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;SheetJS vs ExcelJS — 2026 비교표&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;SheetJS (xlsx) CE&lt;/th&gt;
&lt;th&gt;ExcelJS&lt;/th&gt;
&lt;th&gt;xlsx-populate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;라이선스&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;공식 CDN (&lt;code class=&quot;language-text&quot;&gt;cdn.sheetjs.com&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지원 포맷&lt;/td&gt;
&lt;td&gt;XLSX/XLSM/XLSB/XLS/ODS/CSV&lt;/td&gt;
&lt;td&gt;XLSX/CSV&lt;/td&gt;
&lt;td&gt;XLSX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;읽기 속도&lt;/td&gt;
&lt;td&gt;매우 빠름&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;셀 서식(색/폰트)&lt;/td&gt;
&lt;td&gt;CE는 미지원 (Pro 필요)&lt;/td&gt;
&lt;td&gt;완전 지원&lt;/td&gt;
&lt;td&gt;부분 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;차트&lt;/td&gt;
&lt;td&gt;Pro만&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;번들 크기(gzip)&lt;/td&gt;
&lt;td&gt;약 600KB&lt;/td&gt;
&lt;td&gt;약 900KB&lt;/td&gt;
&lt;td&gt;약 250KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습 곡선&lt;/td&gt;
&lt;td&gt;낮음 (&lt;code class=&quot;language-text&quot;&gt;utils&lt;/code&gt; 중심)&lt;/td&gt;
&lt;td&gt;중간 (객체 모델)&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추천 상황&lt;/td&gt;
&lt;td&gt;읽기·쓰기 위주 관리자 화면&lt;/td&gt;
&lt;td&gt;스타일 지정 리포트&lt;/td&gt;
&lt;td&gt;경량이 우선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;한 문장 요약: &lt;strong&gt;&quot;단순 업/다운로드는 SheetJS, 보기 좋은 리포트는 ExcelJS.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;대용량 파일 주의 — 브라우저 메모리&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;FileReader&lt;/code&gt;로 읽을 수 있는 파일 크기는 이론적으로 수백 MB까지 가능하지만, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;XLSX.read&lt;/code&gt;가 파싱하는 순간 메모리 사용량이 파일 크기의 3~5배&lt;/strong&gt;로 뛰는 경우가 많습니다. 50MB 엑셀이라도 파싱 중에 탭이 멈추는 이유입니다.&lt;/p&gt;
&lt;p&gt;실전 대응 전략:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;10MB가 넘어가면 서버 파싱&lt;/strong&gt;으로 우회. 업로드 후 백엔드(POI, openpyxl 등)에서 처리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSV로 유도&lt;/strong&gt; — 사용자에게 템플릿을 CSV로 제공하면 파싱도 빠르고 메모리도 덜 씁니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스트리밍이 필요하면 Node.js 환경에서 ExcelJS의 &lt;code class=&quot;language-text&quot;&gt;stream.xlsx.WorkbookReader&lt;/code&gt;&lt;/strong&gt; 사용 (브라우저에선 불가).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Worker에서 파싱&lt;/strong&gt; — 메인 스레드가 먹통이 되는 건 막을 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// worker.js&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;importScripts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onmessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;array&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sheet_to_json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sheets&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;wb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SheetNames&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;React 에서 쓰기&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;xlsx&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ExcelExportButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; rows &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ws &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json_to_sheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ws&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!cols&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;wch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;book_new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;book_append_sheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ws&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;목록&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;XLSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;export-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.xlsx&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;compression&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;엑셀 다운로드&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Vue 3 에서 쓰기&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;vue&quot;&gt;&lt;pre class=&quot;language-vue&quot;&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;script setup&amp;gt;
import * as XLSX from &amp;quot;xlsx&amp;quot;

const props = defineProps({ rows: Array })

function download() {
  const ws = XLSX.utils.json_to_sheet(props.rows)
  const wb = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(wb, ws, &amp;quot;목록&amp;quot;)
  XLSX.writeFile(wb, &amp;quot;export.xlsx&amp;quot;, { compression: true })
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&amp;quot;download&amp;quot;&amp;gt;엑셀 다운로드&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. 한글이 깨집니다. 어떻게 해야 하나요?&lt;/h3&gt;
&lt;p&gt;대부분 &lt;strong&gt;CSV로 다운로드할 때&lt;/strong&gt;입니다. Excel이 UTF-8 CSV를 열 때 BOM이 없으면 EUC-KR로 추정해서 깨지는 현상인데, SheetJS는 CSV 쓰기 시 자동으로 BOM을 붙여줍니다. 직접 문자열로 CSV를 만드는 경우라면 앞에 &lt;code class=&quot;language-text&quot;&gt;\uFEFF&lt;/code&gt;를 붙여주세요. XLSX 포맷은 내부적으로 UTF-8이 강제라 한글이 깨질 일이 사실상 없습니다.&lt;/p&gt;
&lt;h3&gt;Q2. 수십 MB짜리 엑셀을 꼭 브라우저에서 처리해야 한다면?&lt;/h3&gt;
&lt;p&gt;솔직히 말씀드리면 &lt;strong&gt;권장하지 않습니다&lt;/strong&gt;. 그래도 해야 한다면 (1) Web Worker로 분리, (2) &lt;code class=&quot;language-text&quot;&gt;sheet_to_json&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;sheet_to_csv&lt;/code&gt;로 스트리밍 파싱, (3) 여러 시트로 분할된 파일이라면 필요한 시트만 &lt;code class=&quot;language-text&quot;&gt;sheetRows&lt;/code&gt; 옵션으로 제한해서 읽기 — 이 세 가지를 조합하세요.&lt;/p&gt;
&lt;h3&gt;Q3. 셀 색/폰트를 꼭 넣어야 한다면 Pro를 사야 하나요?&lt;/h3&gt;
&lt;p&gt;아니요. &lt;strong&gt;ExcelJS(MIT)로 공짜로 가능&lt;/strong&gt;합니다. SheetJS Pro는 차트·피벗·수식 서식 등 고급 기능이 필요한 B2B SaaS에서나 가치가 있고, 일반적인 관리자 페이지 리포트는 ExcelJS로 충분합니다.&lt;/p&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/secure-context-http-https/&quot;&gt;Secure Context — HTTP vs HTTPS와 브라우저 API&lt;/a&gt; — 파일 다운로드 동작에 영향을 줄 수 있는 브라우저 보안 컨텍스트 이슈를 정리했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sheetjs.com/&quot;&gt;SheetJS 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/exceljs/exceljs&quot;&gt;ExcelJS GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dtjohnson/xlsx-populate&quot;&gt;xlsx-populate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;관리자 페이지에서 엑셀 다운로드를 구현해보신 분들, 어떤 라이브러리를 쓰고 계신가요? 저는 읽기·쓰기 비중이 7:3 정도라 여전히 SheetJS를 기본으로 쓰고, 리포트성 요구가 들어올 때만 ExcelJS로 갈아타는 편입니다. 여러분의 선택 기준이 궁금합니다. 댓글로 공유해주세요!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Next.js 15 미들웨어 완벽 가이드 — 인증·리다이렉트·리라이트 실전 패턴]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "로그인 체크를 매 페이지 컴포넌트마다 복붙하고 계신가요? 그 코드, 미들웨어 한 파일로 끝납니다." 결론부터 말씀드리면 — Next.js 1…]]></description><link>https://blog.javapark.kr/middleware/</link><guid isPermaLink="false">https://blog.javapark.kr/middleware/</guid><pubDate>Thu, 14 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;로그인 체크를 매 페이지 컴포넌트마다 복붙하고 계신가요? 그 코드, 미들웨어 한 파일로 끝납니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — &lt;strong&gt;Next.js 15 미들웨어는 요청이 라우트에 도달하기 전에 가로채는 관문&lt;/strong&gt;입니다. 여기서 인증, 리다이렉트, 리라이트, 쿠키 세팅, 봇 차단까지 한 번에 처리하면 각 페이지/레이아웃의 코드가 훨씬 가벼워집니다. 오늘은 2026년 4월 기준 실전에서 바로 쓸 수 있는 &lt;strong&gt;다섯 가지 패턴&lt;/strong&gt;과 &lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt; 설정, Edge Runtime 제약까지 정리해드릴게요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;미들웨어란 무엇인가&lt;/h2&gt;
&lt;p&gt;Next.js 미들웨어는 &lt;strong&gt;요청(Request)을 페이지/API 라우트 핸들러보다 먼저 실행&lt;/strong&gt;하는 함수입니다. 기본적으로 &lt;strong&gt;Edge Runtime&lt;/strong&gt; 위에서 동작하기 때문에 사용자와 물리적으로 가까운 지점(CDN 엣지)에서 실행되고, Node.js 서버 부팅을 기다리지 않아 &lt;strong&gt;콜드 스타트가 거의 없습니다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;쉽게 말하면 —&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;요청을 &lt;strong&gt;통과&lt;/strong&gt;시키거나 (&lt;code class=&quot;language-text&quot;&gt;NextResponse.next()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리다이렉트&lt;/strong&gt; 시키거나 (&lt;code class=&quot;language-text&quot;&gt;NextResponse.redirect()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;내부적으로 &lt;strong&gt;다른 경로로 재작성&lt;/strong&gt;하거나 (&lt;code class=&quot;language-text&quot;&gt;NextResponse.rewrite()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쿠키/헤더&lt;/strong&gt;를 조작한 뒤 계속 진행시키거나&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 네 갈래로 요청 흐름을 제어할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;과거 &lt;code class=&quot;language-text&quot;&gt;pages/_middleware.ts&lt;/code&gt; 또는 per-route 미들웨어 파일은 &lt;strong&gt;deprecated&lt;/strong&gt; 되었습니다. 지금은 &lt;strong&gt;프로젝트 루트의 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 한 파일&lt;/strong&gt;만 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;파일 위치와 기본 구조 (App Router)&lt;/h2&gt;
&lt;p&gt;App Router 프로젝트 기준 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt;는 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;src/&lt;/code&gt; 를 쓰면 &lt;code class=&quot;language-text&quot;&gt;src/middleware.ts&lt;/code&gt;, 아니면 프로젝트 루트&lt;/strong&gt;에 둡니다. &lt;code class=&quot;language-text&quot;&gt;app/&lt;/code&gt; 디렉터리 안에 두는 게 아니라는 점에 주의하세요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;my-app/
├─ app/
│  ├─ layout.tsx
│  └─ page.tsx
├─ middleware.ts   ← 여기!
├─ next.config.ts
└─ package.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;가장 기본적인 스켈레톤은 이렇게 생겼습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 여기서 요청을 가로채서 원하는 작업 수행&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 어떤 경로에서 미들웨어를 실행할지 지정&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/((?!_next/static|_next/image|favicon.ico).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;middleware&lt;/code&gt; 함수는 &lt;strong&gt;default export 없이 named export&lt;/strong&gt; 여야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;NextRequest&lt;/code&gt;는 표준 &lt;code class=&quot;language-text&quot;&gt;Request&lt;/code&gt;의 확장으로 &lt;code class=&quot;language-text&quot;&gt;cookies&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;nextUrl&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;geo&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ip&lt;/code&gt; 등 Next 고유 속성을 추가로 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;config.matcher&lt;/code&gt;는 &lt;strong&gt;빌드 타임에 정적으로 분석&lt;/strong&gt;되기 때문에 환경변수나 런타임 값으로 만들 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;실전 패턴 1 — 인증 가드 + 로그인 페이지 리다이렉트&lt;/h2&gt;
&lt;p&gt;가장 많이 쓰는 케이스입니다. &lt;code class=&quot;language-text&quot;&gt;/dashboard&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/settings&lt;/code&gt; 같은 보호된 경로에 접근할 때 세션 쿠키를 검사하고, 없으면 로그인 페이지로 보냅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/dashboard&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/settings&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/billing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; needsAuth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;needsAuth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;value
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; loginUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    loginUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;from&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 로그인 후 원래 경로로 복귀&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loginUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 필요하면 JWT 검증 (Edge 호환 라이브러리: jose 추천)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/dashboard/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/settings/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/billing/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;포인트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JWT 검증은 &lt;code class=&quot;language-text&quot;&gt;jose&lt;/code&gt;&lt;/strong&gt; 를 쓰세요. &lt;code class=&quot;language-text&quot;&gt;jsonwebtoken&lt;/code&gt;은 Node.js &lt;code class=&quot;language-text&quot;&gt;crypto&lt;/code&gt;에 의존해서 Edge에서 돌지 않습니다.&lt;/li&gt;
&lt;li&gt;DB 조회가 필요한 무거운 인증은 &lt;strong&gt;미들웨어에서 하지 말고&lt;/strong&gt;, 토큰 존재/만료만 체크하고 실제 권한은 Server Component에서 확인하는 게 빠릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;실전 패턴 2 — 지역 기반 i18n 리다이렉트&lt;/h2&gt;
&lt;p&gt;접속자의 국가 코드(&lt;code class=&quot;language-text&quot;&gt;request.geo?.country&lt;/code&gt;)나 &lt;code class=&quot;language-text&quot;&gt;Accept-Language&lt;/code&gt; 헤더를 보고 &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; 요청을 &lt;code class=&quot;language-text&quot;&gt;/ko&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/en&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/ja&lt;/code&gt; 등으로 보냅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LOCALES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ko&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ja&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_LOCALE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;en&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pickLocale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; country &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;geo&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;country&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;country &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;kr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ko&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;country &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ja&quot;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; accept &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;accept-language&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; preferred &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accept&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LOCALES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;preferred &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; preferred &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_LOCALE&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hasLocale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LOCALES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    l &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;l&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;l&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasLocale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; locale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pickLocale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;locale&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;pathname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/((?!_next|api|.*\\..*).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.*\\..*&lt;/code&gt; 패턴으로 &lt;code class=&quot;language-text&quot;&gt;favicon.ico&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;robots.txt&lt;/code&gt; 같은 &lt;strong&gt;정적 파일 확장자 요청은 스킵&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 패턴 3 — A/B 테스트 쿠키 세팅 + 리라이트&lt;/h2&gt;
&lt;p&gt;동일한 URL에 접근해도 사용자를 A/B 버킷에 나누어 &lt;strong&gt;다른 경로를 내부적으로 렌더링&lt;/strong&gt;하고 싶을 때의 패턴입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/pricing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; bucket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ab-pricing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;value
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bucket &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; bucket &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    bucket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bucket &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/pricing-b&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/pricing-a&quot;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rewrite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ab-pricing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bucket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    httpOnly&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sameSite&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lax&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    maxAge&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/pricing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;rewrite&lt;/code&gt;는 &lt;strong&gt;URL은 그대로 유지한 채 내부 경로만 바꾸는&lt;/strong&gt; 기법이라 사용자 주소창에는 &lt;code class=&quot;language-text&quot;&gt;/pricing&lt;/code&gt;으로 보입니다. SEO에도 안전합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 패턴 4 — 봇 차단 / 간단한 Rate Limit&lt;/h2&gt;
&lt;p&gt;Edge에서 돌아가는 KV 스토어(Upstash Redis, Vercel KV 등)와 조합하면 초당 N회 제한을 걸 수 있습니다. 간단한 데모용 코드입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Ratelimit &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@upstash/ratelimit&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Redis &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@upstash/redis&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ratelimit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ratelimit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  redis&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromEnv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  limiter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Ratelimit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slidingWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10 s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 10초당 30회&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BAD_UA&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;(curl|wget|python-requests|scrapy)&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;i&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ua &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user-agent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BAD_UA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ua&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NextResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Forbidden&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;403&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ip &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ip &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-forwarded-for&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anon&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; remaining &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ratelimit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ip&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;success&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NextResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Too Many Requests&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;429&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;X-RateLimit-Remaining&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remaining&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;운영 환경에서는 화이트리스트, 로그인 사용자 예외, 경로별 한도 차등 등을 추가해서 씁니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실전 패턴 5 — 구 URL → 신 URL 리라이트/리다이렉트&lt;/h2&gt;
&lt;p&gt;블로그 구조 개편 등으로 경로가 바뀐 경우, &lt;code class=&quot;language-text&quot;&gt;next.config.ts&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;redirects()&lt;/code&gt;로 대체할 수도 있지만 &lt;strong&gt;동적 규칙&lt;/strong&gt;이 필요하면 미들웨어가 유리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 예: /posts/123 → /blog/123 로 영구 리다이렉트&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^\/posts\/(\d+)$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/blog/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;308&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 301 대신 308(Permanent) 권장&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/posts/:id*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;308은 301과 달리 &lt;strong&gt;메서드/바디를 보존&lt;/strong&gt;하기 때문에 POST 엔드포인트 이전 시에도 안전합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;matcher 설정 깊이 파기&lt;/h2&gt;
&lt;p&gt;미들웨어가 &lt;strong&gt;모든 요청&lt;/strong&gt;에 실행되면 정적 에셋, 이미지 최적화 요청까지 거쳐가서 불필요한 지연이 생깁니다. &lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt;로 꼭 필요한 경로만 태웁니다.&lt;/p&gt;
&lt;p&gt;패턴 예시:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1) 특정 경로만&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;/dashboard/:path*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2) 정적 파일/내부 경로 제외 (가장 흔함)&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3) 헤더 조건까지 조합 (Next 13.5+ 확장 문법)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      source&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/((?!api/).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      has&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;x-admin-preview&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;주의사항:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;matcher는 &lt;strong&gt;빌드 타임 상수&lt;/strong&gt; 여야 합니다. 배열 요소에 변수를 넣지 마세요.&lt;/li&gt;
&lt;li&gt;정규식은 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;path-to-regexp&lt;/code&gt; 문법&lt;/strong&gt;을 따릅니다. 부정 전방탐색 &lt;code class=&quot;language-text&quot;&gt;(?!...)&lt;/code&gt;이 핵심 도구입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Edge Runtime 제약 — 뭘 못 쓰나&lt;/h2&gt;
&lt;p&gt;미들웨어는 기본이 Edge Runtime이라 V8 Isolates 위에서 돕니다. 덕분에 빠르지만 쓸 수 없는 것도 많습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;fs&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;path&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;child_process&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;net&lt;/code&gt; 등 &lt;strong&gt;Node 내장 모듈 다수 불가&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;eval&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;new Function&lt;/code&gt; 같은 &lt;strong&gt;동적 코드 평가 금지&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네이티브 바이너리&lt;/strong&gt;에 의존하는 라이브러리 (예: &lt;code class=&quot;language-text&quot;&gt;bcrypt&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sharp&lt;/code&gt;) 불가&lt;/li&gt;
&lt;li&gt;파일 시스템 접근이 필요한 ORM 초기화도 불가 (Prisma 엣지 클라이언트는 별도)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대체재:&lt;/p&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;JWT 서명/검증&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jsonwebtoken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;jose&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;해시&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;bcrypt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Web Crypto API&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;crypto.subtle&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;uuid&lt;/code&gt; v3&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;crypto.randomUUID()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로깅 DB 쓰기&lt;/td&gt;
&lt;td&gt;Node drive 직결&lt;/td&gt;
&lt;td&gt;HTTP 기반 logger, Upstash&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;Next.js 15.2 이상에서는 &lt;strong&gt;Node.js 런타임 미들웨어&lt;/strong&gt;(&lt;code class=&quot;language-text&quot;&gt;export const config = { runtime: &apos;nodejs&apos; }&lt;/code&gt;)가 실험 기능으로 열려 있습니다. 네이티브 모듈이 꼭 필요하면 고려해볼 수 있지만, Edge의 속도 이점이 사라진다는 점은 감안하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;꼭 알아둘 주의점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;응답 본문은 수정 불가.&lt;/strong&gt; 미들웨어는 HTML 내용을 바꾸는 도구가 아닙니다. 본문 치환이 필요하면 Route Handler나 Server Component에서 하세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쿠키/헤더 조작은 &lt;code class=&quot;language-text&quot;&gt;NextResponse&lt;/code&gt; 객체에만 적용해야 반영됩니다.&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;req.cookies.set()&lt;/code&gt;은 &lt;strong&gt;서버 내부용&lt;/strong&gt;일 뿐, 브라우저에 Set-Cookie로 나가지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;미들웨어 체이닝은 공식 지원되지 않습니다.&lt;/strong&gt; 한 파일에서 여러 관심사를 함수로 쪼개 호출하는 패턴을 쓰세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실행 시간은 짧게.&lt;/strong&gt; Edge에서 수백 ms를 쓰면 TTFB가 크게 악화됩니다. 무거운 로직은 라우트 핸들러로 넘기세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dev 와 prod 매칭이 다를 수 있습니다.&lt;/strong&gt; 배포 전 반드시 &lt;code class=&quot;language-text&quot;&gt;next build &amp;amp;&amp;amp; next start&lt;/code&gt;로 matcher 결과를 확인하세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. 미들웨어 체이닝이 되나요?&lt;/h3&gt;
&lt;p&gt;공식적으로는 &lt;strong&gt;파일 하나만&lt;/strong&gt; 허용됩니다. 대신 각 관심사(auth, i18n, ab-test)를 함수로 분리해서 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 안에서 순차 호출하는 패턴이 일반적입니다. 커뮤니티 라이브러리로 &lt;code class=&quot;language-text&quot;&gt;next-middleware-chain&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;nextjs-middleware-pipeline&lt;/code&gt; 같은 것도 있지만 의존성이 한 겹 더 생기므로 저는 그냥 함수 합성으로 씁니다.&lt;/p&gt;
&lt;h3&gt;Q2. 성능에 영향이 얼마나 가나요?&lt;/h3&gt;
&lt;p&gt;matcher를 잘 건 경우 &lt;strong&gt;요청당 1~5ms&lt;/strong&gt; 수준입니다. 다만 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 호출(DB, 외부 API)이 들어가면 그 지연이 &lt;strong&gt;모든 페이지 TTFB에 누적&lt;/strong&gt;되기 때문에, 가능하면 동기 로직 + 캐싱된 토큰 검증 정도로 제한하세요.&lt;/p&gt;
&lt;h3&gt;Q3. API 라우트에도 미들웨어가 실행되나요?&lt;/h3&gt;
&lt;p&gt;네, &lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt;에 포함되어 있다면 Route Handler(&lt;code class=&quot;language-text&quot;&gt;app/api/.../route.ts&lt;/code&gt;) 실행 &lt;strong&gt;전에&lt;/strong&gt; 돕니다. 인증/레이트리밋을 API에 걸 때 아주 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/middleware-log/&quot;&gt;Next.js 미들웨어로 공통 로그 남기기 — 로깅 특화&lt;/a&gt; — 본 글이 &quot;미들웨어 자체 사용법&quot;에 집중했다면, 로깅 파이프라인 구축은 이 글에서 다룹니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/middleware&quot;&gt;Next.js 공식 문서 — Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/api-reference/edge&quot;&gt;Next.js 공식 문서 — Edge Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/api-reference/functions/next-response&quot;&gt;Next.js 공식 문서 — NextResponse API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/panva/jose&quot;&gt;jose — Edge 호환 JWT 라이브러리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/upstash/ratelimit&quot;&gt;Upstash Ratelimit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;여기까지 Next.js 15 미들웨어의 다섯 가지 실전 패턴과 Edge Runtime 제약을 정리했습니다. 인증, i18n, A/B 테스트, rate limit, URL 마이그레이션 — 네 줄짜리 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 하나로 앱 전체의 관문을 제어할 수 있다는 점이 핵심입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;여러분은 미들웨어를 어떤 용도로 가장 많이 쓰시나요?&lt;/strong&gt; 댓글로 공유해주시면, 다른 분들께도 좋은 레퍼런스가 됩니다. 궁금한 점이나 막히는 부분도 편하게 남겨주세요.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Next.js 15 미들웨어 로깅 — Edge Runtime 에서 요청 추적하기]]></title><description><![CDATA[안녕하세요, 자바파커입니다. 운영 중인 Next.js 앱에서 "어떤 요청이 어디서 느려지는지" 감이 잡히지 않을 때가 있죠.  는 Vercel 대시보드 어딘가에 흩어지고, 서버 액션과 Route Handler…]]></description><link>https://blog.javapark.kr/middleware-log/</link><guid isPermaLink="false">https://blog.javapark.kr/middleware-log/</guid><pubDate>Fri, 10 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;운영 중인 Next.js 앱에서 &quot;어떤 요청이 어디서 느려지는지&quot; 감이 잡히지 않을 때가 있죠. &lt;code class=&quot;language-text&quot;&gt;console.log&lt;/code&gt; 는 Vercel 대시보드 어딘가에 흩어지고, 서버 액션과 Route Handler 가 섞이면 요청 흐름을 한눈에 보기 힘듭니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 — Next.js 15 에서는 &lt;strong&gt;루트의 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 하나&lt;/strong&gt;면 모든 요청 앞단에 로깅 훅을 꽂을 수 있습니다. Edge Runtime 제약만 이해하면 요청 ID 주입, 응답 헤더 태깅, 외부 로그 서비스 전송까지 깔끔하게 통합됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 2023년 글을 2026년에 다시 쓰는 이유&lt;/h2&gt;
&lt;p&gt;3년 전 이 글의 초판은 &lt;code class=&quot;language-text&quot;&gt;server.js&lt;/code&gt; 에서 Express 미들웨어로 로그를 찍는 구조였습니다. 당시에는 그게 정석이었지만 지금은 상황이 완전히 달라졌습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Pages Router (구)&lt;/th&gt;
&lt;th&gt;App Router (Next.js 15)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;로깅 진입점&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;server.js&lt;/code&gt; + Express&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; (루트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 환경&lt;/td&gt;
&lt;td&gt;Node.js 서버&lt;/td&gt;
&lt;td&gt;Edge Runtime 기본&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 위치&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;pages/_middleware.ts&lt;/code&gt; (폐기)&lt;/td&gt;
&lt;td&gt;프로젝트 루트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;커스텀 서버 필요&lt;/td&gt;
&lt;td&gt;Vercel 서버리스/Edge 자동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관찰 도구&lt;/td&gt;
&lt;td&gt;파일 로그, PM2&lt;/td&gt;
&lt;td&gt;Vercel Log Drains, Sentry, Datadog&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pages/_middleware.ts&lt;/code&gt; 는 &lt;strong&gt;Next.js 12.2 에서 deprecated&lt;/strong&gt; 되었고, 커스텀 &lt;code class=&quot;language-text&quot;&gt;server.js&lt;/code&gt; 를 쓰면 Vercel 의 Edge 최적화를 포기하게 됩니다. 그래서 이제는 루트 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 가 표준입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 기본 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 뼈대&lt;/h2&gt;
&lt;p&gt;프로젝트 루트 (= &lt;code class=&quot;language-text&quot;&gt;app/&lt;/code&gt; 과 같은 레벨) 에 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 파일 하나만 만들면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; search &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method

  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;[req] &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;method&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;pathname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;search&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-response-time&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// _next 정적 리소스와 favicon 은 제외&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;/((?!_next/static|_next/image|favicon.ico).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심 포인트 세 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;NextRequest&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;NextResponse&lt;/code&gt;&lt;/strong&gt; — Web 표준 &lt;code class=&quot;language-text&quot;&gt;Request&lt;/code&gt; 의 확장체입니다. &lt;code class=&quot;language-text&quot;&gt;request.nextUrl&lt;/code&gt; 로 파싱된 URL 에 접근합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt;&lt;/strong&gt; — 정규식 기반 경로 필터. 정적 자산을 빼야 로그가 폭주하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge Runtime 기본&lt;/strong&gt; — 별도 설정 없이 전역 엣지에서 실행됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;3. Edge Runtime 제약 — 먼저 알고 가야 합니다&lt;/h2&gt;
&lt;p&gt;미들웨어는 &lt;strong&gt;기본적으로 Edge Runtime&lt;/strong&gt; 에서 돌아갑니다. Node.js API 를 그대로 쓸 수 없다는 뜻입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;th&gt;Edge Runtime&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;console.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용 가능 (Vercel 로그로 수집)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fs&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;path&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;net&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;불가&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;process.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빌드 타임에 인라인된 값만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용 가능 (Web 표준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node &lt;code class=&quot;language-text&quot;&gt;Buffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 시간&lt;/td&gt;
&lt;td&gt;응답 전송 전까지 &lt;strong&gt;수 ms 이내&lt;/strong&gt; 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;즉, 파일 시스템에 로그를 쓰거나 Winston 같은 Node 전용 로거를 바로 불러오면 빌드가 깨집니다. 외부 전송은 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; 로 해야 합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;정 Node API 가 필요하면 &lt;code class=&quot;language-text&quot;&gt;export const runtime = &apos;nodejs&apos;&lt;/code&gt; 를 선언해 Node 런타임으로 전환할 수 있습니다. 단, 콜드 스타트와 비용이 늘어나니 되도록 Edge 를 유지하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;4. 실전 패턴 1 — 요청 ID 주입&lt;/h2&gt;
&lt;p&gt;분산 추적을 하려면 요청마다 고유 ID 가 있어야 합니다. 미들웨어에서 생성해 &lt;strong&gt;요청 헤더와 응답 헤더에 동시에&lt;/strong&gt; 심어 주면 Route Handler, Server Component, 클라이언트 모두 같은 ID 를 볼 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-request-id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 다운스트림에서 읽을 수 있도록 요청 헤더에 주입&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestHeaders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  requestHeaders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-request-id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; requestHeaders &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 클라이언트가 디버깅용으로 볼 수 있도록 응답 헤더에도 노출&lt;/span&gt;
  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-request-id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      level&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      msg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; requestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ua&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user-agent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ip&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-forwarded-for&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  matcher&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/((?!_next/static|_next/image|favicon.ico).*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Server Component 에서는 &lt;code class=&quot;language-text&quot;&gt;next/headers&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;headers()&lt;/code&gt; 로 같은 ID 를 읽습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/any/page.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; headers &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/headers&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; h &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x-request-id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 로그 남길 때 이 ID 를 같이 전송&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;포인트는 &lt;strong&gt;JSON 한 줄로 로그를 찍는 것&lt;/strong&gt; 입니다. Vercel Log Drains, Datadog, Logflare 모두 구조화된 JSON 을 그대로 파싱합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 실전 패턴 2 — Vercel Log Drains 연동&lt;/h2&gt;
&lt;p&gt;Vercel 에 배포하면 &lt;code class=&quot;language-text&quot;&gt;console.log&lt;/code&gt; 는 자동으로 &lt;strong&gt;Runtime Logs&lt;/strong&gt; 에 적재됩니다. 대시보드의 &lt;strong&gt;Observability → Logs&lt;/strong&gt; 에서 실시간 조회가 가능하고, Pro 플랜 이상에서는 &lt;strong&gt;Log Drains&lt;/strong&gt; 로 외부 저장소(S3, Datadog, Axiom, BetterStack 등) 에 스트리밍할 수 있습니다.&lt;/p&gt;
&lt;p&gt;설정 자체는 코드가 아니라 Vercel 대시보드에서 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Project → Settings → &lt;strong&gt;Log Drains&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Destination 선택 (Datadog / Axiom / Custom Webhook)&lt;/li&gt;
&lt;li&gt;필터 조건 설정 (미들웨어만, 특정 경로만 등)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;애플리케이션 코드에서 할 일은 &lt;strong&gt;JSON 로그를 일관된 스키마로 찍는 것&lt;/strong&gt; 뿐입니다. 위 4번 섹션의 JSON 포맷을 그대로 쓰면 Log Drains 에서 그대로 인덱싱됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 실전 패턴 3 — Sentry 로 에러 추적&lt;/h2&gt;
&lt;p&gt;Sentry 는 2025년부터 Next.js 15 App Router 와 Edge Runtime 을 공식 지원합니다. 설치는 한 번입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @sentry/wizard@latest &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; nextjs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;마법사가 &lt;code class=&quot;language-text&quot;&gt;sentry.edge.config.ts&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sentry.server.config.ts&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;sentry.client.config.ts&lt;/code&gt; 세 파일을 생성합니다. 미들웨어에서 예외를 Sentry 로 보내려면 다음 패턴을 씁니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// middleware.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Sentry &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@sentry/nextjs&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextResponse &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; NextRequest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/server&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NextRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... 인증/리다이렉트 등 로직&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Sentry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      tags&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; layer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;middleware&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      extra&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NextResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;internal&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;sentry.edge.config.ts&lt;/code&gt; 덕분에 Edge Runtime 에서도 정상 작동합니다. 4번 섹션의 요청 ID 를 Sentry tag 로 함께 보내면 로그-트레이스 상관관계 분석이 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7. 흔한 실수 체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정적 자산까지 로깅&lt;/strong&gt; — &lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt; 누락으로 &lt;code class=&quot;language-text&quot;&gt;_next/static/*&lt;/code&gt; 가 전부 찍혀 로그 요금 폭탄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동기 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt; 등으로 응답 지연&lt;/strong&gt; — 미들웨어는 &lt;strong&gt;모든 요청의 앞단&lt;/strong&gt; 입니다. 외부 전송은 &lt;code class=&quot;language-text&quot;&gt;waitUntil&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; 를 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 하지 말고 fire-and-forget 패턴으로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;환경변수 런타임 변경 기대&lt;/strong&gt; — Edge 에서는 빌드 시점에 인라인됩니다. 런타임 변경은 Node 런타임에서만&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;pages/_middleware.ts&lt;/code&gt; 혼용&lt;/strong&gt; — 완전히 제거된 API 입니다. 루트 &lt;code class=&quot;language-text&quot;&gt;middleware.ts&lt;/code&gt; 하나만&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q1. Edge Runtime 에서 &lt;code class=&quot;language-text&quot;&gt;console.log&lt;/code&gt; 가 로컬 터미널에 안 보이면?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;로컬 &lt;code class=&quot;language-text&quot;&gt;next dev&lt;/code&gt; 는 터미널에 그대로 출력됩니다. 만약 보이지 않는다면 &lt;code class=&quot;language-text&quot;&gt;matcher&lt;/code&gt; 에서 해당 경로가 제외됐거나, 빌드 캐시 이슈일 확률이 높습니다. &lt;code class=&quot;language-text&quot;&gt;.next&lt;/code&gt; 폴더 삭제 후 재시작해 보세요. 배포 환경에서는 Vercel 대시보드의 Runtime Logs 를 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q2. Sentry 말고 Datadog APM 을 쓰고 싶어요.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Datadog 은 서버 런타임용 Node 라이브러리(&lt;code class=&quot;language-text&quot;&gt;dd-trace&lt;/code&gt;) 와 Edge 용 로그 수집을 구분합니다. 미들웨어(Edge) 에서는 &lt;strong&gt;Vercel Log Drains → Datadog Logs&lt;/strong&gt; 경로를 쓰고, Route Handler 에서 &lt;code class=&quot;language-text&quot;&gt;export const runtime = &apos;nodejs&apos;&lt;/code&gt; 를 선언한 구간만 &lt;code class=&quot;language-text&quot;&gt;dd-trace&lt;/code&gt; 로 APM 계측하는 분리 전략이 실무에서 가장 깔끔합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q3. 미들웨어 로깅이 성능에 영향을 주지 않나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;console.log&lt;/code&gt; 한 줄과 헤더 주입 정도는 &lt;strong&gt;1ms 미만&lt;/strong&gt; 입니다. 다만 외부 API 호출을 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 하면 전체 응답이 그만큼 지연됩니다. 외부 전송은 Log Drains 같은 비동기 경로를 쓰고, 미들웨어는 로컬 버퍼링만 담당하게 설계하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/middleware/&quot;&gt;Next.js 미들웨어 기본 개념 정리&lt;/a&gt; — 이 글이 로깅 중심이라면, 원리부터 보고 싶다면 이쪽을 추천합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/middleware&quot;&gt;Next.js 공식 문서 — Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/api-reference/edge&quot;&gt;Next.js Edge Runtime API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/docs/observability/runtime-logs&quot;&gt;Vercel — Runtime Logs &amp;#x26; Log Drains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/&quot;&gt;Sentry for Next.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;여기까지가 2026년 현재 Next.js 15 에서 미들웨어 로깅을 실무에 적용하는 최소 구성입니다. 여러분은 어떤 로그 스택을 쓰고 계신가요? Axiom, BetterStack, Datadog 중 궁합이 좋았던 조합이 있다면 댓글로 공유해 주세요. 궁금한 점이나 막히는 부분도 편하게 남겨 주시면 같이 풀어 보겠습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[VuePress 시작하기 — 2026 기준 설치부터 배포까지]]></title><description><![CDATA[안녕하세요, 자바파커입니다. "Vue 로 기술문서 사이트를 만들고 싶은데, 지금 2026년에도 VuePress 를 써도 되나요?" 라는 질문을 자주 받습니다. 결론부터 말씀드리면 —
VuePress v…]]></description><link>https://blog.javapark.kr/getting-started/</link><guid isPermaLink="false">https://blog.javapark.kr/getting-started/</guid><pubDate>Wed, 26 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요, &lt;strong&gt;자바파커&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Vue 로 기술문서 사이트를 만들고 싶은데, 지금 2026년에도 VuePress 를 써도 되나요?&quot; 라는 질문을 자주 받습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결론부터 말씀드리면 —
&lt;strong&gt;VuePress v2 는 여전히 정상적으로 유지되고 동작하지만, 2026년 현재 Vue 팀이 공식적으로 추천하는 SSG 는 VitePress&lt;/strong&gt; 입니다. 기존 VuePress 프로젝트를 유지보수 중이라면 그대로 써도 무방하고, 신규로 시작한다면 VitePress 쪽을 먼저 검토하시길 권합니다. 그래도 VuePress 기본기는 Vue 기반 SSG 를 이해하는 데 가장 빠른 지름길이라 오늘은 VuePress 를 기준으로 정리해 보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;VuePress 가 뭔가요?&lt;/h2&gt;
&lt;p&gt;간단히 말해, &lt;strong&gt;마크다운 파일을 예쁜 정적 웹사이트로 변환해주는 Vue.js 기반 도구&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue.js 로 개발된 &lt;strong&gt;정적 사이트 생성기 (SSG)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;기술문서에 최적화된 &lt;strong&gt;기본 테마&lt;/strong&gt; 제공 (검색, 사이드바, 다국어 등)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugin API&lt;/strong&gt; 로 Google Analytics, PWA, SEO 등을 손쉽게 확장&lt;/li&gt;
&lt;li&gt;마크다운 안에서 Vue 컴포넌트를 그대로 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;일상 비유로는, 워드 문서 폴더 하나를 통째로 잘 정돈된 기술 블로그로 바꿔주는 컨베이어 벨트 같은 녀석이라고 보시면 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;VuePress vs VitePress, 뭐가 다른가?&lt;/h2&gt;
&lt;p&gt;솔직하게 비교해 보겠습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;VuePress v2&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;VitePress&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;빌드 도구&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;webpack 기반 (옵션으로 Vite)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Vite 기반 (기본)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;속도&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;준수&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;매우 빠름 (HMR 체감 2~5배)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;플러그인 생태계&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;풍부함&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;점진적으로 확대 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Vue 팀 공식 추천&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;유지되고 있음&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;O (2026 현재)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;러닝 커브&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;낮음&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;VuePress 가 &quot;구식&quot; 은 아닙니다. 다만 Vue 팀이 VitePress 쪽으로 무게를 옮긴 상태이므로, 새 프로젝트라면 VitePress 를 먼저 보시는 게 합리적입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;설치 및 빌드 (2026 기준)&lt;/h2&gt;
&lt;p&gt;Node.js 는 &lt;strong&gt;LTS (20.x 이상)&lt;/strong&gt; 권장합니다. 패키지 매니저는 pnpm 을 기본으로, npm 도 병기합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 프로젝트 초기화&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; my-docs &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-docs
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; init            &lt;span class=&quot;token comment&quot;&gt;# 또는: npm init -y&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# VuePress v2 로컬 설치&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; vuepress@next @vuepress/client@next vue
&lt;span class=&quot;token comment&quot;&gt;# npm 의 경우&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# npm install -D vuepress@next @vuepress/client@next vue&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 문서 디렉토리와 첫 페이지 생성&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; docs
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;# Hello VuePress&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; docs/README.md&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; 에 스크립트를 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;docs:dev&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vuepress dev docs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;docs:build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vuepress build docs&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;개발 서버 실행 및 빌드:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 개발 모드 (http://localhost:8080)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; docs:dev

&lt;span class=&quot;token comment&quot;&gt;# 정적 파일 빌드 (docs/.vuepress/dist)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; docs:build&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;디렉토리 구조&lt;/h2&gt;
&lt;p&gt;VuePress 에서 권장하는 구조입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;.
├── docs
│   ├── .vuepress            (옵션)
│   │   ├── components       (Vue 컴포넌트)
│   │   ├── theme            (커스텀 테마)
│   │   ├── public           (정적 리소스)
│   │   ├── styles           (스타일)
│   │   ├── config.ts        (메인 설정, TS 권장)
│   │   └── client.ts        (클라이언트 확장)
│   ├── README.md            (루트 페이지)
│   ├── guide
│   │   └── README.md
│   └── config.md
└── package.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;2026 기준으로는 &lt;code class=&quot;language-text&quot;&gt;config.js&lt;/code&gt; 대신 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;config.ts&lt;/code&gt;&lt;/strong&gt; 로 시작하는 것을 추천합니다. 타입 힌트가 붙어서 설정 실수가 확 줄어듭니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;페이지 라우팅 규칙&lt;/h2&gt;
&lt;p&gt;타겟 디렉토리는 &lt;code class=&quot;language-text&quot;&gt;docs&lt;/code&gt; 이고, 아래 경로는 모두 &lt;code class=&quot;language-text&quot;&gt;docs&lt;/code&gt; 기준입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;파일 경로&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;URL 경로&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/README.md&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/guide/README.md&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/guide/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/config.md&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/config.html&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;알아두면 좋은 마크다운 확장&lt;/h2&gt;
&lt;h3&gt;코드 하이라이팅&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;js&lt;/span&gt;
&lt;span class=&quot;token code-block language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MyComponent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;줄 번호 표시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// docs/.vuepress/config.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; defineUserConfig &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vuepress&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineUserConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  markdown&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lineNumbers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;커스텀 컨테이너&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;::: tip
이것은 팁 블록입니다.
:::

::: warning
경고 블록도 가능합니다.
:::&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;배포하기&lt;/h2&gt;
&lt;h3&gt;GitHub Pages (GitHub Actions)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docs/.vuepress/config.ts&lt;/code&gt; 에 &lt;code class=&quot;language-text&quot;&gt;base&lt;/code&gt; 설정을 잊지 마세요. &lt;code class=&quot;language-text&quot;&gt;https://&amp;lt;USER&gt;.github.io/&amp;lt;REPO&gt;&lt;/code&gt; 라면 &lt;code class=&quot;language-text&quot;&gt;base: &apos;/&amp;lt;REPO&gt;/&apos;&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.github/workflows/deploy.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy VuePress

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; read
  &lt;span class=&quot;token key atrule&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write
  &lt;span class=&quot;token key atrule&quot;&gt;id-token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build-and-deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pnpm

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pnpm/action&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;setup@v4
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pnpm install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;frozen&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lockfile
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pnpm docs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;build

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;artifact@v3
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; docs/.vuepress/dist

  &lt;span class=&quot;token key atrule&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;and&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;deploy
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deployment
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Netlify&lt;/h3&gt;
&lt;p&gt;Netlify 는 2026년에도 VuePress 와 궁합이 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build command:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;pnpm docs:build&lt;/code&gt; (또는 &lt;code class=&quot;language-text&quot;&gt;npm run docs:build&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish directory:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;docs/.vuepress/dist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node version:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;20&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;푸시만 해도 자동 배포되니, 개인 프로젝트라면 Netlify 가 가장 편합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Q1. 2026년에 VuePress 새로 시작해도 되나요?&lt;/h3&gt;
&lt;p&gt;유지는 되지만, &lt;strong&gt;새로 시작한다면 VitePress 를 먼저 검토&lt;/strong&gt;하시길 권합니다. 공식 추천, 빌드 속도, 최신 생태계 모두 VitePress 쪽이 앞서 있습니다. 기존 VuePress 프로젝트를 굳이 갈아엎을 필요까지는 없습니다.&lt;/p&gt;
&lt;h3&gt;Q2. React 를 쓰는데 VuePress 를 써도 되나요?&lt;/h3&gt;
&lt;p&gt;결과물은 정적 HTML 이라 사용자 입장에서는 문제가 없지만, 커스터마이징 시 Vue 컴포넌트 작성이 필요합니다. React 라면 Docusaurus, Nextra 쪽이 더 자연스럽습니다.&lt;/p&gt;
&lt;h3&gt;Q3. 블로그 용도로도 괜찮나요?&lt;/h3&gt;
&lt;p&gt;기술문서에 최적화되어 있어 블로그로 쓰려면 &lt;code class=&quot;language-text&quot;&gt;@vuepress/plugin-blog&lt;/code&gt; 같은 플러그인이 필요합니다. 블로그가 메인이라면 Gatsby, Astro, Next.js 가 더 편합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;관련 포스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/getting-started/&quot;&gt;GitHub 블로그 시작하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/git/&quot;&gt;Git 기본기 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vuepress.vuejs.org/&quot;&gt;VuePress 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vitepress.dev/&quot;&gt;VitePress 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/&quot;&gt;Vue.js 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;VuePress 는 여전히 Vue 기반 SSG 의 교과서 같은 도구입니다. 입문용으로도, 사내 기술문서용으로도 충분히 제 역할을 합니다. 다만 2026년 기준으로 새 프로젝트라면 VitePress 도 꼭 비교해 보세요.&lt;/p&gt;
&lt;p&gt;여러분은 기술문서 도구로 어떤 걸 쓰고 계신가요? 댓글로 경험 공유해 주시면 다음 포스팅 주제로 반영하겠습니다.&lt;/p&gt;</content:encoded></item></channel></rss>