<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko-KR"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://dev.drawyourmind.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dev.drawyourmind.com/" rel="alternate" type="text/html" hreflang="ko-KR" /><updated>2026-05-20T04:35:30+00:00</updated><id>https://dev.drawyourmind.com/feed.xml</id><title type="html">dev.note</title><subtitle>프론트엔드 개발자 박정무의 기술 블로그. JavaScript, TypeScript, React, Next.js, Vue 등 웹 개발 관련 학습 기록과 실무 노하우를 공유합니다.
</subtitle><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><entry><title type="html">와이프의 풍선장식 사업, 남편이 직접 홈페이지 제작한 후기</title><link href="https://dev.drawyourmind.com/posts/blanc-belluno-launch-review/" rel="alternate" type="text/html" title="와이프의 풍선장식 사업, 남편이 직접 홈페이지 제작한 후기" /><published>2026-05-20T00:00:00+00:00</published><updated>2026-05-20T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/blanc-belluno-launch-review</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/blanc-belluno-launch-review/"><![CDATA[<h2 id="네-줄-요약">네 줄 요약</h2>

<ul>
  <li>아내가 운영하는 파티풍선 장식 브랜드 <strong>블랑벨루노(Blanc Belluno)</strong> 의 홈페이지를 런칭했다</li>
  <li><strong>스택</strong>: Next.js 16 (App Router, Turbopack) + React 19 + TypeScript + Tailwind CSS 4 + Supabase + Vercel</li>
  <li><strong>연동</strong>: 인스타그램 Graph API + 솔라피 카카오 알림톡</li>
  <li>핵심은 <strong>“와이프가 직접 손쉽게 운영할 수 있는 사이트”</strong> — 관리자 페이지에서 포트폴리오 및 히어로 슬라이드를 드래그 앤 드롭으로 관리</li>
</ul>

<hr />

<h2 id="1-왜-만들었나">1. 왜 만들었나</h2>

<p>와이프는 파티풍선 장식 브랜드 <strong>블랑벨루노</strong>를 운영하고 있습니다. 돌잔치, 베이비샤워, 브랜드 팝업 등 행사 풍선장식이 주력입니다.</p>

<p>문제는 모든 문의가 인스타그램 DM을 비롯한 특정 채널로만 들어온다는 점이었습니다.</p>

<ul>
  <li>문의가 들어와도 검색되지 않음 → 신규 유입은 오직 해시태그 운, 인스타그램 알고리즘 운</li>
  <li>견적 안내, 예약 가능일, 포트폴리오를 매번 DM으로 똑같이 설명</li>
  <li>행사 사진은 인스타에 다 있지만, 정작 포트폴리오 페이지가 없음</li>
  <li>무엇보다 브랜드 검색 시 인스타그램, 네이버 스마트스토어 외에는 노출되는 자산이 없음</li>
</ul>

<p>소상공인에게 인스타그램은 훌륭한 채널이지만, <strong>자산은 아닙니다</strong>. 그래서 결정했습니다. <em>“만들자.”</em></p>

<hr />

<h2 id="2-1주-안에-끝낸다">2. “1주 안에 끝낸다”</h2>

<blockquote>
  <p>“완벽한 사이트 6개월” 보다 “쓸 만한 사이트 1주”가 100배 낫다.</p>
</blockquote>

<p>직장을 다니면서 만드는 사이드 프로젝트는 대체로 시간이 부족하기 마련입니다. 그래서 첫날 가장 먼저 한 일은 <strong>“이번 스프린트에 안 만들 것”</strong> 을 정하는 것이었습니다.</p>

<ul>
  <li>❌ <strong>결제 시스템</strong> → 어차피 행사 견적은 1:1 상담이 필수</li>
  <li>❌ <strong>회원가입/로그인</strong> → 5페이지짜리 사이트에 불필요</li>
  <li>❌ <strong>블로그 시스템</strong> → 별도 채널에서 운영 중</li>
  <li>❌ <strong>다국어</strong> → 타깃은 국내 고객</li>
</ul>

<p>MVP의 본질은 <strong>“지금 당장 안 만들어도 되는 것”을 가려내는 일</strong>입니다. 이렇게 1주 안에 런칭할 범위를 좁혔습니다.</p>

<hr />

<h2 id="3-기술-스택과-선택-이유">3. 기술 스택과 선택 이유</h2>

<p>단순합니다. <strong>제가 익숙한 스택이기 때문입니다.</strong></p>

<h3 id="nextjs--turbopack--vercel">Next.js + Turbopack + Vercel</h3>

<ul>
  <li><strong>Next.js</strong>: SSG/ISR로 SEO를 잡고, 이미지 최적화(<code class="language-plaintext highlighter-rouge">next/image</code>)가 기본 — 풍선장식 사이트는 이미지가 생명</li>
  <li><strong>Turbopack</strong>: 개발 중 빌드 속도가 체감상 다름</li>
  <li><strong>Vercel</strong>: 배포·도메인 연결이 간단하고, 호스팅 비용 0원으로 시작 가능</li>
</ul>

<h3 id="tailwind-css-4">Tailwind CSS 4</h3>

<p>Tailwind 4의 <code class="language-plaintext highlighter-rouge">@theme</code> 디렉티브로 <strong>파스텔 톤 커스텀 팔레트 + 글래스모피즘</strong>을 디자인 시스템 토큰으로 잡았습니다. 파티풍선 장식 브랜드 톤(부드러움, 우아함)을 클래스 한 줄로 일관되게 적용할 수 있습니다.</p>

<h3 id="supabase--postgresql--storage">Supabase — PostgreSQL + Storage</h3>

<p>이번 작업의 <strong>숨은 주인공</strong>입니다.</p>

<ul>
  <li><strong>PostgreSQL</strong>: 포트폴리오, 히어로 슬라이드, 상담 문의 데이터 모두 저장</li>
  <li><strong>Storage</strong>: 직접 업로드한 작업 이미지 호스팅</li>
  <li><strong>Firebase보다 Supabase를 고른 이유</strong>: PostgreSQL이라서. 나중에 데이터 분석을 하거나 다른 시스템으로 옮길 때 표준 SQL로 다룰 수 있다는 점이 장기적으로 큽니다.</li>
</ul>

<h3 id="인스타그램-graph-api--suspense-스트리밍">인스타그램 Graph API + <code class="language-plaintext highlighter-rouge">&lt;Suspense&gt;</code> 스트리밍</h3>

<blockquote>
  <p>“와이프가 인스타그램에 올린 피드가 홈페이지에서도 보여야 한다.”</p>
</blockquote>

<ul>
  <li>Instagram Graph API로 최근 게시물 fetch</li>
  <li>React 19 <code class="language-plaintext highlighter-rouge">&lt;Suspense&gt;</code>로 스트리밍 렌더링 — 인스타그램 API가 느려도 페이지 나머지는 즉시 표시</li>
</ul>

<p>이게 중요한 이유는, 인스타그램 API가 가끔 느릴 때가 있기 때문입니다. 그때마다 페이지 전체가 멈춰 보이면 사용자 경험이 크게 나빠집니다.</p>

<h3 id="솔라피solapi-카카오-알림톡--양방향-발송">솔라피(Solapi) 카카오 알림톡 — 양방향 발송</h3>

<p>상담 폼 제출 시 자동으로 발송됩니다.</p>

<ul>
  <li><strong>고객에게</strong>: “상담 접수 확인” 알림톡</li>
  <li><strong>관리자에게</strong>: “신규 상담 알림” + 고객 정보 요약 알림톡</li>
</ul>

<h3 id="isr--홈페이지-5분-단위-재생성">ISR — 홈페이지 5분 단위 재생성</h3>

<p><code class="language-plaintext highlighter-rouge">revalidate = 300</code>. 와이프가 관리자 페이지에서 포트폴리오나 히어로 슬라이드를 수정하면 최대 5분 안에 홈페이지에 반영됩니다.</p>

<ul>
  <li>매번 빌드를 트리거할 필요 없음</li>
  <li>매 요청마다 SSR을 돌리지도 않음</li>
  <li><strong>정적 사이트의 속도와 동적 사이트의 신선도를 동시에 잡는 패턴</strong></li>
</ul>

<hr />

<h2 id="4-핵심-기능-4가지">4. 핵심 기능 4가지</h2>

<h3 id="-포트폴리오--8개-카테고리--라이트박스">① 포트폴리오 — 8개 카테고리 + 라이트박스</h3>

<p>각 카테고리별 페이지에서 작업물을 갤러리 형태로 보여주고, Swiper 기반 라이트박스 모달로 확대 감상이 가능합니다.</p>

<h3 id="-서비스-상세-페이지--정적-생성으로-seo-극대화">② 서비스 상세 페이지 — 정적 생성으로 SEO 극대화</h3>

<p><code class="language-plaintext highlighter-rouge">/services/[slug]</code> 패턴으로 카테고리별 서비스 상세 페이지를 <strong>빌드 타임에 정적 생성</strong>했습니다.</p>

<h3 id="-상담-문의--db-저장--카카오-알림톡">③ 상담 문의 — DB 저장 + 카카오 알림톡</h3>

<ul>
  <li>문의 폼은 <strong>최소 필드</strong>로 구성</li>
  <li>제출 즉시 다음이 실행됩니다.
    <ul>
      <li>Supabase DB 저장</li>
      <li>고객에게 접수 확인 알림톡 발송</li>
      <li>관리자에게 신규 상담 알림톡 발송</li>
    </ul>
  </li>
</ul>

<h3 id="-관리자-페이지--와이프가-직접-운영하는-페이지">④ 관리자 페이지 — 와이프가 직접 운영하는 페이지</h3>

<p><strong>핵심 기능입니다.</strong> <code class="language-plaintext highlighter-rouge">/admin</code> 경로에 들어가면 다음을 사용할 수 있습니다.</p>

<ul>
  <li><strong>포트폴리오 관리</strong>: 사진 업로드(Supabase Storage 자동), 카테고리 지정, <code class="language-plaintext highlighter-rouge">@dnd-kit</code>으로 드래그 앤 드롭 정렬</li>
  <li><strong>히어로 슬라이드 관리</strong>: 메인 페이지 상단 슬라이더 이미지/순서 변경</li>
  <li><strong>사이트 설정</strong>: 영업시간, 연락처 등 메타 정보</li>
</ul>

<p>와이프가 새 작업물 사진을 추가하고 싶을 때, <strong>저를 호출하지 않도록</strong> 한 것이죠ㅋㅋ.</p>

<blockquote>
  <p>이게 안 되면 사이트는 1년 안에 죽는다고 봅니다.</p>
</blockquote>

<hr />

<h2 id="5-런칭-후">5. 런칭 후</h2>

<p>런칭한 지 얼마 안 됐지만, 체감은 확실합니다.</p>

<ul>
  <li><strong>상담 시간 단축</strong>: “가격이요?”, “포트폴리오 있어요?” 같은 질문이 사라짐</li>
  <li><strong>신뢰도 상승</strong>: 인스타만 보내던 예전과 “공식 홈페이지를 확인해 주세요”라고 안내하는 지금은 다른 비즈니스</li>
  <li><strong>검색 자산화 시작</strong>: 네이버·구글에 “블랑벨루노”를 검색하면 인스타 외의 자산이 노출되기 시작</li>
  <li><strong>운영 부담 0</strong>: 와이프가 관리자 페이지에서 직접 콘텐츠 업데이트</li>
</ul>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="Next.js" /><category term="Supabase" /><category term="사이드프로젝트" /><summary type="html"><![CDATA[Next.js 16 + Supabase + Vercel로 1주 만에 런칭한 아내 브랜드 블랑벨루노 홈페이지 제작기]]></summary></entry><entry><title type="html">협업 중 안전하게 force push 하기</title><link href="https://dev.drawyourmind.com/posts/force-with-lease/" rel="alternate" type="text/html" title="협업 중 안전하게 force push 하기" /><published>2025-07-17T00:00:00+00:00</published><updated>2025-07-17T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/force-with-lease</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/force-with-lease/"><![CDATA[<p>로컬에서 커밋 스쿼시나 rebase 한 다음 푸시하려다 보니 force 옵션 쓸 일이 생김.<br />
근데 그냥 –force 쓰면 협업 중에 위험할 수 있음.<br />
다른 사람이 올린 커밋을 모르게 날려버릴 수도 있음.</p>

<p>그럴 때는 <code class="language-plaintext highlighter-rouge">--force-with-lease</code> 옵션 사용하는 게 훨씬 안전함.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push --force-with-lease origin master
</code></pre></div></div>

<p>이 명령어는,</p>

<ul>
  <li>“원격 브랜치 상태가 내가 마지막으로 본 상태랑 동일할 때만 푸시 허용”</li>
  <li>조건이 붙어 있어서 누군가 먼저 커밋 올렸으면 푸시가 거부됨.</li>
  <li>실수 방지용 안전장치라고 보면 됨.</li>
</ul>

<hr />

<p>force push 관련 명령어 정리</p>

<p><strong>명령어 설명</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">git push --force origin master</code> 무조건 강제 푸시. 위험함.</li>
  <li><code class="language-plaintext highlighter-rouge">git push --force-with-lease origin master</code> 안전한 강제 푸시. 협업 시 권장.</li>
  <li><code class="language-plaintext highlighter-rouge">git push -f origin master</code> –force 축약형. 위험도 동일함.</li>
</ul>

<p>혼자 작업 중이면 상관 없지만, 협업 중이면 –force-with-lease 쓰는 게 맞음.<br />
습관처럼 써두는 게 좋음.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="git" /><summary type="html"><![CDATA[강제 푸시가 필요한 상황에서 실수 없이 안전하게 사용하는 방법]]></summary></entry><entry><title type="html">CursorAI 활용 가이드 - 사이드 프로젝트를 빠르게 완성하는 3단계</title><link href="https://dev.drawyourmind.com/posts/cursorai-project-guide/" rel="alternate" type="text/html" title="CursorAI 활용 가이드 - 사이드 프로젝트를 빠르게 완성하는 3단계" /><published>2025-01-24T00:00:00+00:00</published><updated>2025-01-24T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/cursorai-project-guide</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/cursorai-project-guide/"><![CDATA[<p>Microsoft의 엔지니어 <a href="https://www.linkedin.com/in/zohaibrauf/"><strong>Zohaib Rauf</strong></a>는 CursorAI와 LLM을 활용해 그동안 미뤄왔던 다양한 사이드 프로젝트를 빠르게 제작했다고 합니다. 오늘은 그가 추천한 방법을 바탕으로 CursorAI를 효과적으로 활용하는 3단계를 소개해 드리겠습니다.</p>

<h2 id="1단계-스펙-구상하기">1단계: <strong>스펙 구상하기</strong></h2>

<p>먼저 <strong>GPT</strong>를 활용해 애플리케이션의 스펙을 구체화하세요. 명확하고 구체적인 기술 명세서를 작성하면 프로젝트 진행이 훨씬 수월해집니다.<br />
이때 다음과 같은 요청을 GPT에 활용할 수 있습니다:</p>

<blockquote>
  <p>“다른 사람이나 AI가 이 문서를 보고 웹사이트를 만들 수 있도록 markdown 형식의 명세서를 만들어줘. 프로젝트 세부사항, UX/UI, 각 기능에 필요한 것들을 반드시 포함해야 해. 기술은 TypeScript, React, TailwindCSS 위주로 사용할 거야.”</p>
</blockquote>

<p><strong>TIP:</strong></p>

<ul>
  <li>작성된 스펙은 반드시 별도로 저장해 <strong>SPEC.md</strong> 파일로 관리하세요.</li>
  <li>나중에 이 문서를 참조하면서 프로젝트를 체계적으로 진행할 수 있습니다.</li>
</ul>

<h2 id="2단계-초기-코드-생성하기">2단계: <strong>초기 코드 생성하기</strong></h2>

<p>Vite 등으로 프로젝트를 시작한 후, <strong>SPEC.md 파일</strong>을 프로젝트 폴더에 추가하세요.<br />
Cursor IDE에서 다음과 같은 단계를 따라 초기 코드를 생성할 수 있습니다:</p>

<ol>
  <li><strong>Cursor IDE</strong> &gt; Composer &gt; Select Agent로 이동</li>
  <li>SPEC.md를 컨텍스트로 추가</li>
  <li>구현 요청</li>
</ol>

<p>몇 분 안에 기본적인 뼈대가 완성되며, 초기 코드 작성 시간을 크게 단축할 수 있습니다.</p>

<h2 id="3단계-작은-단위로-기능-쪼개기">3단계: <strong>작은 단위로 기능 쪼개기</strong></h2>

<p>세부 기능을 구현할 때는 <strong>작은 단위로 나누어 요청</strong>하는 것이 중요합니다.</p>

<blockquote>
  <p>큰 단위로 요청하면 오류가 발생할 가능성이 높고, 수정하는 데 더 많은 시간이 소요됩니다.</p>
</blockquote>

<ul>
  <li>기능별로 작업을 나누어 요청하세요.</li>
  <li>기존 코드나 문서 링크를 적극 활용해 컨텍스트로 제공하세요.</li>
  <li>이렇게 하면 AI의 환각(hallucination)을 방지하고 정확도를 높일 수 있습니다.</li>
</ul>

<hr />

<h2 id="cursorai-활용-꿀팁">CursorAI 활용 꿀팁</h2>

<h3 id="1-llm-혼합-사용">1. LLM 혼합 사용</h3>

<p>Zohaib는 기능 초안을 작성할 때 <strong>o1</strong>을, 반복 작업은 <strong>Claude-3.5-sonnet</strong>을 활용한다고 합니다. 여러분도 프로젝트 성격에 맞게 다양한 LLM을 활용해 보세요.</p>

<h3 id="2-v0dev로-ux-쉽게-구현">2. v0.dev로 UX 쉽게 구현</h3>

<p>v0.dev 같은 툴을 사용하면 UX/UI 작업 시간을 줄이고 효율성을 극대화할 수 있습니다.</p>

<p><img src="https://vercel.com/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F3CSHPVw6n6ZPBWXZfFycpp%2Fb499dbf7977ad404be660a892da2100a%2F600x300.png&amp;w=1920&amp;q=75" alt="v0.dev screenshot" /></p>

<h3 id="3-프로젝트-디렉토리-관리">3. 프로젝트 디렉토리 관리</h3>

<ul>
  <li><strong>SPEC.md</strong>: 프로젝트 관련 내용을 체계적으로 관리</li>
  <li><strong>.cursorrules</strong> 파일: 프로젝트에서 사용할 기술 스택을 지정하거나 제외할 기술을 명시</li>
</ul>

<hr />

<h2 id="cursorai로-프로젝트를-빠르게-완성하세요">CursorAI로 프로젝트를 빠르게 완성하세요</h2>

<p>CursorAI와 LLM을 활용하면 프로젝트 진행 속도가 빨라지고 효율성이 높아집니다. 그러나 AI가 작성한 코드를 제대로 이해하지 못하면 디버깅에 어려움을 겪을 수 있으니, <strong>코드를 파악하며 작업을 진행하는 것</strong>도 잊지 마세요.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="IDE" /><category term="AI" /><summary type="html"><![CDATA[CursorAI와 LLM을 활용해 사이드 프로젝트를 빠르고 효율적으로 완성하는 3단계 가이드]]></summary></entry><entry><title type="html">AI 시대, 40대 개발자의 생존 전략과 커리어에 대한 생각</title><link href="https://dev.drawyourmind.com/posts/senior-developer-survival/" rel="alternate" type="text/html" title="AI 시대, 40대 개발자의 생존 전략과 커리어에 대한 생각" /><published>2024-12-10T00:00:00+00:00</published><updated>2024-12-10T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/senior-developer-survival</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/senior-developer-survival/"><![CDATA[<p>AI가 점점 더 세상을 바꾸고 있다. 개발자라는 직업도 예외가 아니다. 요즘 주변에서 40대 개발자로 살아가는 게 쉽지 않다는 얘기를 자주 듣는다. AI가 다 하는데 내가 필요한 걸까? 같은 고민을 하는 사람이 많다.</p>

<p>사실 40대라는 시점은 인생에서도, 커리어에서도 중요한 시기다. 이미 많은 경험을 쌓았지만, 새로운 기술을 배우는 게 조금은 부담스러울 수 있다. 하지만 이런 시대에 살아남는 방법은 분명히 있다.</p>

<hr />

<h2 id="ai-시대-개발자로서의-기회와-도전">AI 시대, 개발자로서의 기회와 도전</h2>

<h3 id="1-ai가-바꾼-개발-생태계">1. AI가 바꾼 개발 생태계</h3>

<p>AI 덕분에 세상은 더 똑똑해지고 있다. 코딩 없이도 개발이 가능해지는 툴이 늘어나고, 자동화가 많은 일을 대신해주고 있다. 그래서 “개발자가 필요 없어질까?”라는 걱정을 하는 사람도 많다.</p>

<p>하지만 이런 변화 속에서도 우리가 가진 건 있다. 40대 개발자는 수십 년간 쌓아온 경험과 인사이트를 무기로 삼을 수 있다. 프로젝트를 성공으로 이끈 노하우나 네트워크는 AI가 따라 하기 힘든 부분이다.</p>

<hr />

<h3 id="2-40대-개발자의-강점-살리기">2. 40대 개발자의 강점 살리기</h3>

<p>40대 개발자로서 가장 큰 무기는 경험과 문제 해결 능력이다. 이런 능력은 시간이 지나면서 더 빛을 발한다. 게다가 네트워킹으로 얻을 수 있는 기회도 많다. 그러니까 “경험은 무기다”라는 생각으로 더 적극적으로 나서야 한다.</p>

<hr />

<h2 id="40대-개발자의-스킬-셋-재정비">40대 개발자의 스킬 셋 재정비</h2>

<h3 id="1-ai-관련-기술-배우기">1. AI 관련 기술 배우기</h3>

<p>AI가 주도하는 시대인 만큼 관련 기술을 조금이라도 알아두는 게 좋다. 머신러닝, 데이터 분석 같은 기술은 어려워 보이지만, 기본적인 개념만 이해해도 큰 도움이 된다. 요즘은 무료로 배울 수 있는 강의도 많으니까 부담 가지지 말고 한 번 도전해보자.</p>

<hr />

<h3 id="2-기존-언어와-새로운-기술의-밸런스-찾기">2. 기존 언어와 새로운 기술의 밸런스 찾기</h3>

<p>40대에 새로운 언어를 배우는 건 쉬운 일이 아니다. 하지만 AI와 관련된 기술과 기존의 언어를 어떻게 연결할지 고민해보면 길이 보인다. 예를 들어, Python을 이미 알고 있다면 AI 라이브러리를 활용하는 법을 배우는 식으로 기존 지식을 확장하면 된다.</p>

<hr />

<h2 id="지속-가능한-커리어-플랜-만들기">지속 가능한 커리어 플랜 만들기</h2>

<h3 id="1-평생-학습과-자기계발">1. 평생 학습과 자기계발</h3>

<p>AI 시대에도 성공하려면 배움을 멈추지 않는 게 중요하다. 새로운 기술만 배우는 게 아니라, 내가 잘하는 걸 더 잘하게 만드는 것도 중요하다.</p>

<hr />

<h3 id="2-안정적인-커리어를-위한-네트워킹">2. 안정적인 커리어를 위한 네트워킹</h3>

<p>지금까지 쌓아온 네트워크를 더 적극적으로 활용해보자. 모임에 나가서 새로운 사람들을 만나고, 내 경험을 나누다 보면 예상치 못한 기회가 생길 수도 있다. 이정도는 시니어 개발자라면 누구나 할 수 있는 일이다.</p>

<hr />

<h2 id="결론">결론</h2>

<p>AI 시대에 40대 개발자로 살아가는 건 도전일 수 있지만, 시니어 개발자가 가지고 있는 경험과 강점을 잘 활용하면 충분히 해낼 수 있다. 중요한 건 새로운 기술을 두려워하지 않고 배우려는 자세와 꾸준한 노력이다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="개발자" /><category term="커리어" /><summary type="html"><![CDATA[시니어 개발자의 커리어 관리에 대하여]]></summary></entry><entry><title type="html">웹사이트에서 리다이렉트가 SEO에 미치는 영향과 해결 방법</title><link href="https://dev.drawyourmind.com/posts/seo-app-redirect/" rel="alternate" type="text/html" title="웹사이트에서 리다이렉트가 SEO에 미치는 영향과 해결 방법" /><published>2024-11-08T00:00:00+00:00</published><updated>2024-11-08T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/seo-app-redirect</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/seo-app-redirect/"><![CDATA[<p>SEO(검색 엔진 최적화) 관점에서 웹사이트 진입 시에 앱 또는 웹으로 리다이렉트될 경우 검색 엔진 크롤러가 페이지를 정상적으로 크롤링하지 못해 검색 노출에 불이익을 줄 수 있습니다. 웹사이트 진입 시 리다이렉트를 어떻게 하면 SEO 손실을 최소화하면서도 효율적으로 운영할 수 있는지 알아보겠습니다.</p>

<h3 id="1-user-agent를-이용한-리다이렉트-제어치">1. User-Agent를 이용한 리다이렉트 제어치</h3>

<p>검색 엔진 크롤러와 실제 사용자를 구분하여 크롤러가 접근할 때는 웹페이지를 리다이렉트 없이 그대로 보여주고, 일반 사용자에게만 앱 리다이렉트를 제공할 수 있습니다. 예를 들어, Googlebot과 같은 크롤러에게는 웹페이지를 정상적으로 노출해 검색 엔진에서 페이지가 제대로 인덱싱될 수 있도록 하는 것입니다.</p>

<blockquote>
  <p><strong>Tip</strong>: 서버에서 User-Agent를 검사해 Googlebot 및 기타 주요 크롤러(User-Agent)를 구별하고, 크롤러가 접근할 때는 리다이렉트를 적용하지 않도록 설정할 수 있습니다.</p>
</blockquote>

<h3 id="2-브라우저-확인-모달-또는-배너-제공">2. 브라우저 확인 모달 또는 배너 제공</h3>

<p>리다이렉트 대신 앱 설치나 이동을 유도하는 배너를 웹페이지 상단이나 하단에 제공하여 SEO 손실을 줄이면서도 앱 설치를 유도할 수 있습니다. 사용자 경험을 저해하지 않으면서 자연스럽게 앱 이동을 권장할 수 있습니다.</p>

<h4 id="smart-app-banner-활용하기">“Smart App Banner” 활용하기</h4>

<p>모바일 웹 페이지에서 iOS의 경우 HTML 메타 태그를 이용한 “Smart App Banner”를 활용할 수 있습니다. 이를 통해 사용자에게 앱 설치를 권장하면서도 웹페이지 콘텐츠를 그대로 유지할 수 있습니다.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"apple-itunes-app"</span> <span class="na">content=</span><span class="s">"app-id=앱ID"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<h3 id="3-앱-링크-활용하기">3. 앱 링크 활용하기</h3>

<p>웹페이지에 앱 링크를 설정하여 특정 링크를 클릭할 때 앱이 설치된 경우 앱에서 열리도록 설정할 수 있습니다. 이를 통해 검색 엔진 크롤링을 유지하면서도 사용자가 웹사이트에서 앱으로 전환할 수 있도록 유도할 수 있습니다.</p>

<h4 id="android의-예시">Android의 예시</h4>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"intent://페이지#Intent;scheme=앱Scheme;package=com.example;end"</span>
  <span class="nt">&gt;</span>앱으로 열기<span class="nt">&lt;/a</span>
<span class="nt">&gt;</span>
</code></pre></div></div>

<h4 id="ios의-예시">iOS의 예시</h4>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"your-app-scheme://"</span><span class="nt">&gt;</span>앱으로 열기<span class="nt">&lt;/a&gt;</span>
</code></pre></div></div>

<h3 id="4-javascript로-지연-로드하기">4. JavaScript로 지연 로드하기</h3>

<p>페이지 초기 로딩 시에는 웹페이지를 그대로 노출한 후, JavaScript를 통해 앱 리다이렉트 알림을 띄우거나 리다이렉트를 처리하는 방식입니다. 이는 검색 엔진이 웹페이지 콘텐츠를 인덱싱할 수 있도록 돕는 동시에 사용자에게 앱 설치 유도를 할 수 있는 장점이 있습니다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">your-app-scheme://</span><span class="dl">'</span><span class="p">;</span>
  <span class="p">},</span> <span class="mi">3000</span><span class="p">);</span> <span class="c1">// 페이지 로딩 후 3초 뒤 앱 리다이렉트 실행</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="5-웹과-앱의-콘텐츠-일관성-유지">5. 웹과 앱의 콘텐츠 일관성 유지</h3>

<p>검색 엔진 크롤러가 접근하는 웹페이지와 실제 사용자가 보는 앱의 콘텐츠가 일치하도록 구성하면 SEO에도 유리하게 작용할 수 있습니다. 콘텐츠의 일관성은 사이트의 신뢰도를 높이고, 사용자가 웹사이트와 앱 모두에서 같은 경험을 하도록 해줍니다. 또한, 이는 검색 엔진이 페이지를 정확하게 이해하는 데도 도움이 됩니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="SEO" /><summary type="html"><![CDATA[리다이렉트가 SEO에 미치는 영향과 이를 해결하는 방법에 대해 알아봅니다.]]></summary></entry><entry><title type="html">Turbopack 웹 애플리케이션 성능을 높이는 차세대 번들러</title><link href="https://dev.drawyourmind.com/posts/introduction_to_turbopack/" rel="alternate" type="text/html" title="Turbopack 웹 애플리케이션 성능을 높이는 차세대 번들러" /><published>2024-11-01T00:00:00+00:00</published><updated>2024-11-01T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/introduction_to_turbopack</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/introduction_to_turbopack/"><![CDATA[<p>웹 애플리케이션 성능의 핵심 요소 중 하나는 <strong>빌드 속도와 번들 효율성</strong>입니다. Vercel에서 개발한 <strong>Turbopack</strong>은 기존 번들러인 Webpack의 한계를 극복하고 성능을 획기적으로 향상하기 위해 개발되었습니다.</p>

<hr />

<h2 id="turbopack이란">Turbopack이란?</h2>

<p>Turbopack은 Next.js를 개발한 Vercel에서 출시한 <strong>고성능 웹 애플리케이션 번들러</strong>입니다. Rust 언어로 구현되어 초고속 빌드 속도를 제공하며, 특히 <strong>대규모 프로젝트에서 높은 효율성</strong>을 발휘합니다. Next.js 15 버전부터는 Turbopack이 기본 번들러로 채택되어, 성능 최적화가 더욱 쉬워졌습니다.</p>

<hr />

<h2 id="turbopack의-주요-기능과-특징">Turbopack의 주요 기능과 특징</h2>

<h3 id="1-초고속-빌드-속도">1. <strong>초고속 빌드 속도</strong></h3>

<p>Turbopack은 Rust의 고성능 특성을 활용하여 기존의 Webpack에 비해 매우 빠른 빌드 속도를 제공합니다. 특히 개발 환경에서 코드를 수정했을 때 실시간으로 반영하는 <strong>HMR(Hot Module Replacement)</strong> 기능이 대폭 개선되어, <strong>즉각적인 피드백</strong>이 가능합니다.</p>

<h3 id="2-증분-빌드incremetal-bundling">2. <strong>증분 빌드(Incremetal Bundling)</strong></h3>

<p>프로젝트 내 모든 파일을 매번 번들링하지 않고, 변경된 부분만 업데이트하는 <strong>증분 빌드</strong> 방식을 채택하고 있습니다. 이를 통해 <strong>빌드 시간을 획기적으로 단축</strong>하여 개발 생산성을 높이며, 특히 파일 수가 많은 대규모 프로젝트에서도 효과적입니다.</p>

<h3 id="3-최신-트리-쉐이킹-및-코드-스플리팅">3. <strong>최신 트리 쉐이킹 및 코드 스플리팅</strong></h3>

<p>Turbopack은 불필요한 코드 제거를 자동으로 수행하는 <strong>트리 쉐이킹</strong>과 <strong>코드 스플리팅</strong>을 지원하여, 번들 크기를 줄여줍니다. 덕분에 최종 빌드 파일이 작아져 <strong>페이지 로딩 속도가 빨라지고 사용자 경험이 개선</strong>됩니다.</p>

<h3 id="4-nextjs와의-완벽한-통합">4. <strong>Next.js와의 완벽한 통합</strong></h3>

<p>Next.js와 Turbopack은 같은 Vercel 팀에서 개발한 만큼, Next.js와의 통합이 자연스럽고 최적화되어 있습니다. Next.js 프로젝트에서 Turbopack을 사용할 경우 설정이 매우 간단하며, 특히 SSR(서버 사이드 렌더링)에서 성능이 더욱 극대화됩니다.</p>

<hr />

<h2 id="turbopack의-장점">Turbopack의 장점</h2>

<ul>
  <li><strong>Rust 기반의 빠르고 안정적인 성능</strong>: Rust로 작성되어 메모리 관리가 우수하고 성능이 뛰어납니다.</li>
  <li><strong>개발자 경험 향상</strong>: 즉각적인 피드백과 빠른 빌드로 개발 속도가 증가합니다.</li>
  <li><strong>대규모 프로젝트에 적합</strong>: 증분 빌드를 통해 프로젝트 규모에 상관없이 빠르게 빌드가 가능합니다.</li>
  <li><strong>Next.js와의 최적화된 통합</strong>: Next.js 프로젝트에서 성능을 극대화할 수 있습니다.</li>
</ul>

<hr />

<h2 id="아직은-개발-초기-단계">아직은 개발 초기 단계</h2>

<p>Turbopack은 Webpack이나 Vite처럼 오랜 시간 동안 사용되어 온 번들러들에 비해 생태계가 덜 성숙한 편입니다. 특히 일부 복잡한 플러그인과 설정이 지원되지 않을 수 있습니다. 그러나 Vercel은 꾸준한 업데이트와 기능 추가를 통해 Turbopack을 빠르게 발전시키고 있습니다.</p>

<hr />

<h2 id="결론-웹-애플리케이션-성능의-새로운-길">결론: 웹 애플리케이션 성능의 새로운 길</h2>

<p>Turbopack은 Next.js와 같은 현대적 웹 프레임워크와 함께 사용하여 <strong>웹 애플리케이션 성능을 극대화</strong>하는 도구로 자리 잡아가고 있습니다. 특히 Next.js 15 버전 이상을 사용하는 개발자라면 Turbopack을 통해 더 빠르고 효율적인 개발 경험을 누릴 수 있습니다. Turbopack이 제공하는 속도와 효율성으로 프로젝트의 개발 생산성을 높일 수 있습니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="Turbopack" /><category term="Next.js" /><category term="Bundler" /><summary type="html"><![CDATA[Next.js 15에서 도입된 차세대 번들러 Turbopack에 대해 알아보기]]></summary></entry><entry><title type="html">Next.js 15 주요 업데이트와 새로운 기능</title><link href="https://dev.drawyourmind.com/posts/nextjs15-feature-overview/" rel="alternate" type="text/html" title="Next.js 15 주요 업데이트와 새로운 기능" /><published>2024-11-01T00:00:00+00:00</published><updated>2024-11-01T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/nextjs15-feature-overview</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/nextjs15-feature-overview/"><![CDATA[<p>Next.js 15 버전은 최신 웹 개발 트렌드를 반영하여 많은 기능이 추가되고 기존 기능이 개선되었습니다. 주요 업데이트는 다음과 같습니다.</p>

<h3 id="1-react-19-지원">1. <strong>React 19 지원</strong></h3>

<ul>
  <li>Next.js 15은 React 19을 완전히 지원하여 최신 React 기능들을 활용할 수 있게 되었습니다. 컴포넌트 성능 최적화와 새로운 렌더링 패턴을 지원해, 보다 빠르고 효율적인 React 사용이 가능합니다.</li>
</ul>

<h3 id="2-turbopack의-안정화">2. <strong>Turbopack의 안정화</strong></h3>

<ul>
  <li>Next.js 15에서는 웹팩 대신 <strong>Turbopack</strong>이 기본 개발 번들러로 자리 잡았어요. Turbopack은 빌드 속도가 훨씬 빠르고 효율적이어서 특히 대규모 애플리케이션 개발 시 유리합니다. Next.js 14 버전에서 초기 도입된 Turbopack이 이번 버전에서 안정화되어 더욱 믿고 사용할 수 있게 되었죠.</li>
</ul>

<h3 id="3-최신-캐싱-정책">3. <strong>최신 캐싱 정책</strong></h3>

<ul>
  <li>새로운 <strong>정적 및 동적 캐싱 정책</strong>이 도입되어 데이터 요청 시 더 빠른 응답과 효율적인 자원 관리가 가능해졌습니다. 특히, ‘SSR(서버 사이드 렌더링)’ 및 ‘ISR(증분 정적 재생성)’에서 캐싱 방식이 개선되어 페이지 로딩 시간이 크게 줄어들고 유연해졌습니다.</li>
</ul>

<h3 id="4-개선된-라우팅-기능">4. <strong>개선된 라우팅 기능</strong></h3>

<ul>
  <li>Next.js 15에서는 라우팅 시스템이 더 직관적이고 사용하기 쉬워졌습니다. 특히, <strong>App Router</strong> 기능을 통해 서버 컴포넌트와 클라이언트 컴포넌트를 혼합하여 사용할 수 있어, 기존의 API Routes와 Pages 구조에서 벗어난 새로운 컴포넌트 기반 라우팅을 제공합니다.</li>
</ul>

<h3 id="5-향상된-이미지-최적화">5. <strong>향상된 이미지 최적화</strong></h3>

<ul>
  <li>이미지 최적화 도구도 크게 개선되었는데, CDN을 사용하지 않고도 로컬에서 최적화된 이미지를 제공할 수 있으며, 새로운 API를 통해 이미지 관련 최적화를 세세하게 조정할 수 있게 되었습니다.</li>
</ul>

<h3 id="6-보안-및-접근성-강화">6. <strong>보안 및 접근성 강화</strong></h3>

<ul>
  <li>웹 접근성 개선과 보안 업데이트도 강화되었습니다. 자동으로 설정되는 <strong>Content Security Policy(CSP)</strong>와 기타 보안 헤더가 추가되어 더 안전한 웹 애플리케이션을 만들 수 있게 되었습니다.</li>
</ul>

<h3 id="7-react-서버-컴포넌트와의-통합">7. <strong>React 서버 컴포넌트와의 통합</strong></h3>

<ul>
  <li>Next.js 15에서는 React 서버 컴포넌트를 더욱 쉽게 활용할 수 있도록 최적화가 이루어졌습니다. 이 기능을 통해 초기 로딩 속도가 빨라지고 클라이언트와 서버 간의 데이터 교환이 더욱 원활해졌습니다.</li>
</ul>

<h3 id="요약하자면">요약하자면</h3>

<p>Next.js 15은 <strong>성능, 개발 경험, 보안</strong> 면에서 상당한 업그레이드를 제공하는 버전입니다. 특히 대형 프로젝트나 높은 트래픽을 예상하는 서비스라면, 이번 업데이트로 더욱 빠르고 안정적으로 웹 애플리케이션을 운영할 수 있습니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="Next.js" /><category term="React" /><summary type="html"><![CDATA[React 19 지원, 향상된 라우팅, Turbopack 개선, 효율적인 캐싱 전략 등 성능과 개발자 경험을 높여줄 다양한 업데이트]]></summary></entry><entry><title type="html">안드로이드 디바이스에서 popstate 이벤트를 사용하는 방법</title><link href="https://dev.drawyourmind.com/posts/popstate-on-android/" rel="alternate" type="text/html" title="안드로이드 디바이스에서 popstate 이벤트를 사용하는 방법" /><published>2024-09-09T00:00:00+00:00</published><updated>2024-09-09T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/popstate-on-android</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/popstate-on-android/"><![CDATA[<p>안드로이드 디바이스에서 웹 애플리케이션을 개발할 때, <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트는 매우 유용한 기능입니다. 이 이벤트는 사용자가 브라우저의 “뒤로 가기” 버튼을 눌렀을 때 발생하며, 특히 SPA(Single Page Application) 환경에서 페이지 전환을 보다 자연스럽게 처리할 수 있게 해줍니다.</p>

<h3 id="popstate-이벤트의-목적"><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트의 목적</h3>

<p>웹 애플리케이션에서 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트를 사용하는 주요 목적은 다음과 같습니다.</p>

<ol>
  <li><strong>뒤로 가기 동작 제어</strong>: 사용자가 브라우저의 뒤로 가기 버튼을 눌렀을 때, 애플리케이션이 제대로 반응하도록 도와줍니다. 예를 들어, 특정 섹션이나 모달을 닫거나, SPA의 페이지 전환을 처리할 수 있습니다.</li>
  <li><strong>히스토리 관리</strong>: <code class="language-plaintext highlighter-rouge">history.pushState()</code> 또는 <code class="language-plaintext highlighter-rouge">history.replaceState()</code>를 사용하여 브라우저의 주소창을 업데이트한 경우, <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트를 사용하여 해당 상태를 복원할 수 있습니다.</li>
  <li><strong>유저 경험 개선</strong>: 페이지를 새로 로드하지 않고 URL을 변경하여 사용자에게 더 빠르고 매끄러운 네비게이션 경험을 제공합니다.</li>
</ol>

<h3 id="popstate-이벤트-사용-방법"><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트 사용 방법</h3>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html&gt;</span>
  <span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;title&gt;</span>Popstate 예제<span class="nt">&lt;/title&gt;</span>
  <span class="nt">&lt;/head&gt;</span>
  <span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;h1&gt;</span>Popstate 이벤트 예제<span class="nt">&lt;/h1&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"pushStateButton"</span><span class="nt">&gt;</span>페이지 상태 추가<span class="nt">&lt;/button&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"goBackButton"</span><span class="nt">&gt;</span>뒤로 가기<span class="nt">&lt;/button&gt;</span>

    <span class="nt">&lt;script&gt;</span>
      <span class="c1">// popstate 이벤트 핸들러 설정</span>
      <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">popstate</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">alert</span><span class="p">(</span><span class="s2">`이전 상태로 복귀: </span><span class="p">${</span><span class="nx">event</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">page</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">이전 상태가 없음</span><span class="dl">'</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">});</span>

      <span class="c1">// pushState 버튼 클릭 시 새로운 상태 추가</span>
      <span class="nb">document</span>
        <span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">pushStateButton</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span> <span class="na">page</span><span class="p">:</span> <span class="dl">'</span><span class="s1">새로운 상태</span><span class="dl">'</span> <span class="p">};</span>
          <span class="nx">history</span><span class="p">.</span><span class="nx">pushState</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">'</span><span class="s1">?newPage</span><span class="dl">'</span><span class="p">);</span>
          <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">새로운 상태가 추가되었습니다.</span><span class="dl">'</span><span class="p">);</span>
        <span class="p">});</span>

      <span class="c1">// goBack 버튼 클릭 시 뒤로 가기</span>
      <span class="nb">document</span>
        <span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">goBackButton</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
          <span class="nx">history</span><span class="p">.</span><span class="nx">back</span><span class="p">();</span>
        <span class="p">});</span>
    <span class="nt">&lt;/script&gt;</span>
  <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<h3 id="주요-메서드-설명">주요 메서드 설명</h3>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">history.pushState(state, title, url)</code></strong>: 브라우저의 히스토리에 새로운 상태를 추가합니다. 이때 상태 객체, 페이지 제목, 새로운 URL을 지정할 수 있습니다.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">history.replaceState(state, title, url)</code></strong>: 현재 히스토리 항목을 새로운 상태로 교체합니다.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">history.back()</code></strong>: 브라우저 히스토리에서 뒤로 이동합니다.</li>
</ul>

<h3 id="popstate-이벤트의-특성"><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트의 특성</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트는 <code class="language-plaintext highlighter-rouge">pushState</code>나 <code class="language-plaintext highlighter-rouge">replaceState</code> 메서드로 히스토리가 변경될 때 자동으로 발생하지 않습니다. 브라우저의 뒤로 가기, 앞으로 가기 동작에만 반응합니다.</li>
  <li><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트의 <code class="language-plaintext highlighter-rouge">event.state</code>에는 <code class="language-plaintext highlighter-rouge">pushState</code>나 <code class="language-plaintext highlighter-rouge">replaceState</code>로 추가된 상태 정보가 포함됩니다.</li>
</ul>

<h3 id="주의사항">주의사항</h3>

<p>안드로이드 브라우저나 크롬에서 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트는 정상적으로 동작하지만, 일부 구형 브라우저에서는 호환성 문제가 있을 수 있습니다. 이를 방지하기 위해 폴리필(polyfill)을 사용하는 방법도 고려해야 합니다.</p>

<h3 id="결론">결론</h3>

<p>안드로이드 디바이스에서 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트를 적절히 사용하면, SPA 환경에서 뒤로 가기 버튼을 눌렀을 때의 네비게이션 문제를 해결하고 사용자 경험을 크게 향상시킬 수 있습니다. 이 이벤트는 브라우저 히스토리 관리와 더불어 동적 페이지 전환을 처리하는 데 매우 유용하므로, 다양한 상황에서 적극적으로 활용할 수 있습니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="android" /><category term="popstate" /><summary type="html"><![CDATA[popstate 이벤트는 뒤로, 앞으로 가기 동작 시 페이지의 상태를 관리한다]]></summary></entry><entry><title type="html">autocomplete 속성으로 웹 폼 최적화하기</title><link href="https://dev.drawyourmind.com/posts/autocomplete/" rel="alternate" type="text/html" title="autocomplete 속성으로 웹 폼 최적화하기" /><published>2024-08-29T00:00:00+00:00</published><updated>2024-08-29T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/autocomplete</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/autocomplete/"><![CDATA[<p>브라우저의 자동완성 기능이 검색 화면 내 내가 만든 자동검색어 창과 겹쳐서 두 개의 자동완성 요소가 화면에 나타나는 이슈가 발생했습니다. 이를 해결하기 위해 HTML의 <code class="language-plaintext highlighter-rouge">autocomplete</code> 속성을 활용하여 브라우저의 기본 자동완성 기능을 비활성화하고, 내가 만든 자동검색어 창만 작동하도록 설정했습니다.</p>

<p>HTML의 <code class="language-plaintext highlighter-rouge">&lt;input&gt;</code> 태그에서 <code class="language-plaintext highlighter-rouge">autocomplete</code> 속성은 브라우저가 사용자에게 이전에 입력한 값을 기반으로 자동으로 완성된 값을 제안할지 여부를 결정하는 속성입니다. 이 속성은 사용자 경험을 향상시켜, 사용자가 폼을 더 빠르게 작성할 수 있도록 돕습니다.</p>

<h3 id="autocomplete-속성의-값"><code class="language-plaintext highlighter-rouge">autocomplete</code> 속성의 값</h3>

<ol>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">on</code></strong>: 기본 값으로, 브라우저가 가능한 자동완성 데이터를 제공하게 합니다.</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">autocomplete=</span><span class="s">"on"</span> <span class="nt">/&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">off</code></strong>: 자동완성 기능을 비활성화합니다. 사용자가 이전에 입력한 데이터를 기반으로 한 자동완성 제안이 나타나지 않도록 설정합니다.</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">autocomplete=</span><span class="s">"off"</span> <span class="nt">/&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>특정 값들</strong>: <code class="language-plaintext highlighter-rouge">autocomplete</code> 속성은 더 세부적인 값들을 설정할 수 있으며, 브라우저는 이 값을 기준으로 더 구체적인 자동완성 데이터를 제안합니다.</p>

    <p>예를 들어:</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">autocomplete=</span><span class="s">"email"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;input</span>
  <span class="na">type=</span><span class="s">"password"</span>
  <span class="na">name=</span><span class="s">"current-password"</span>
  <span class="na">autocomplete=</span><span class="s">"current-password"</span>
<span class="nt">/&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"tel"</span> <span class="na">name=</span><span class="s">"phone"</span> <span class="na">autocomplete=</span><span class="s">"tel"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"name"</span> <span class="na">name=</span><span class="s">"fullname"</span> <span class="na">autocomplete=</span><span class="s">"name"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"address"</span> <span class="na">name=</span><span class="s">"address"</span> <span class="na">autocomplete=</span><span class="s">"address-line1"</span> <span class="nt">/&gt;</span>
</code></pre></div>    </div>

    <p>이러한 값들은 브라우저가 해당 필드의 목적을 이해하도록 돕고, 자동완성 기능이 정확한 데이터를 제공할 수 있게 합니다.</p>
  </li>
</ol>

<h3 id="autocomplete-속성의-일반적인-사용-사례"><code class="language-plaintext highlighter-rouge">autocomplete</code> 속성의 일반적인 사용 사례</h3>

<ul>
  <li><strong>폼 최적화</strong>: 사용자가 이전에 작성한 정보(예: 이메일, 주소, 이름 등)를 저장하고, 다음 번에 동일한 폼을 작성할 때 자동으로 제안하여 시간을 절약하게 할 수 있습니다.</li>
  <li><strong>보안</strong>: 자동완성을 비활성화(<code class="language-plaintext highlighter-rouge">off</code>) 하여 중요한 정보(예: 비밀번호, 신용카드 정보 등)가 자동완성으로 제안되지 않도록 할 수 있습니다.</li>
  <li><strong>사용자 경험</strong>: 사용자 입력 오류를 줄이고, 특히 모바일 환경에서 입력을 더 편리하게 만듭니다.</li>
</ul>

<h3 id="주의사항">주의사항</h3>

<ul>
  <li><strong>개인정보 보호</strong>: 자동완성을 사용할 때는 사용자 개인정보가 보호될 수 있도록 고려해야 합니다. 예를 들어, 공용 컴퓨터에서는 자동완성이 비활성화되도록 설정하는 것이 좋습니다.</li>
  <li><strong>브라우저 지원</strong>: 대부분의 최신 브라우저는 <code class="language-plaintext highlighter-rouge">autocomplete</code> 속성을 지원하지만, 일부 구형 브라우저에서는 예상한 대로 동작하지 않을 수 있습니다.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">autocomplete</code> 속성을 적절히 활용하면, 폼 입력 시 사용자 경험을 크게 개선할 수 있으며, 특정 상황에서는 보안성을 강화할 수 있습니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="html" /><summary type="html"><![CDATA[autocomplete 속성을 활용해 웹 폼의 사용자 경험과 보안을 향상시키는 방법]]></summary></entry><entry><title type="html">아이폰(iOS)에서 Input 자동 focus가 동작하지 않는 이유와 대처법</title><link href="https://dev.drawyourmind.com/posts/ios-input-issue/" rel="alternate" type="text/html" title="아이폰(iOS)에서 Input 자동 focus가 동작하지 않는 이유와 대처법" /><published>2024-08-29T00:00:00+00:00</published><updated>2024-08-29T00:00:00+00:00</updated><id>https://dev.drawyourmind.com/posts/ios-input-issue</id><content type="html" xml:base="https://dev.drawyourmind.com/posts/ios-input-issue/"><![CDATA[<h3 id="ios에서-자동-포커스-설정이-어려운-이유와-해결-방법">iOS에서 자동 포커스 설정이 어려운 이유와 해결 방법</h3>

<p>웹 개발을 하다 보면 사용자에게 더 나은 경험을 제공하기 위해 특정 <code class="language-plaintext highlighter-rouge">input</code> 요소에 자동으로 포커스를 주고 싶을 때가 있습니다. 예를 들어, 검색창에 자동으로 포커스가 가도록 하여 사용자가 페이지를 로드하자마자 바로 검색을 시작할 수 있게 하는 것이죠. 하지만 iOS에서는 이런 자동 포커스 설정이 생각만큼 쉽게 이루어지지 않습니다.</p>

<h3 id="ios에서-자동-포커스가-제한되는-이유">iOS에서 자동 포커스가 제한되는 이유</h3>

<p>iOS에서는 사용자가 직접 화면을 터치하지 않는 한, <code class="language-plaintext highlighter-rouge">input</code> 요소에 자동으로 포커스를 설정하는 것이 제한됩니다. 이는 주로 사용자 경험을 보호하고, 의도하지 않은 자동화된 동작이 발생하는 것을 방지하기 위해서입니다. 예를 들어, 사용자가 예상치 못한 상황에서 키보드가 자동으로 올라오는 것을 방지하여 혼란을 줄이려는 목적이 있습니다.</p>

<p>특히, 주소가 변경되면서 새로운 페이지가 로드되는 경우, 이 새로운 페이지에서는 <code class="language-plaintext highlighter-rouge">input</code> 요소에 자동으로 포커스를 줄 수 없습니다. 이는 예를 들어 검색창을 클릭하여 <code class="language-plaintext highlighter-rouge">/search</code>와 같은 주소로 이동했을 때, 새로운 페이지에서 다시 검색창에 자동으로 포커스를 주기 어려운 상황을 의미합니다.</p>

<h3 id="문제-해결-방법">문제 해결 방법</h3>

<p>이러한 제한에도 불구하고, 몇 가지 방법을 통해 사용자 경험을 개선할 수 있습니다. 다음은 iOS에서 자동 포커스 설정이 어려운 상황을 해결하기 위한 방법들입니다.</p>

<h3 id="1-사용자의-직접적인-인터랙션-유도">1. 사용자의 직접적인 인터랙션 유도</h3>

<p>가장 확실한 방법은 사용자가 페이지 로드 후 특정 영역을 직접 클릭하게 유도하는 것입니다. 예를 들어, 페이지가 로드된 후 검색창을 다시 한 번 클릭하면 그때 포커스를 줄 수 있습니다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">searchField</span><span class="dl">'</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>

<p>이 방법은 사용자가 페이지 로드 후 검색창을 다시 한 번 클릭하게 유도하는 방식입니다. 사용자가 직접적으로 검색창을 클릭하면, iOS에서도 포커스를 설정할 수 있습니다.</p>

<h3 id="2-페이지-전환-없이-검색-ui-전환">2. 페이지 전환 없이 검색 UI 전환</h3>

<p>페이지 전환 없이, 한 페이지 내에서 검색 화면을 전환하는 방법도 고려해볼 수 있습니다. SPA(Single Page Application)처럼 페이지 내에서 UI를 전환하면, 같은 페이지 내에서 <code class="language-plaintext highlighter-rouge">focus()</code> 메서드를 사용할 수 있습니다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">showSearch</span><span class="p">()</span> <span class="p">{</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">searchSection</span><span class="dl">'</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">block</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">searchField</span><span class="dl">'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
  <span class="p">},</span> <span class="mi">300</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이 방법은 페이지를 이동시키지 않고, 검색 영역을 보여주는 애니메이션이나 전환 효과를 사용하면서 포커스를 설정하는 방식입니다.</p>

<h3 id="3-autofocus-속성과-타임아웃-사용">3. <code class="language-plaintext highlighter-rouge">autofocus</code> 속성과 타임아웃 사용</h3>

<p><code class="language-plaintext highlighter-rouge">autofocus</code> 속성을 사용해보는 것도 하나의 방법입니다. 하지만 iOS에서는 이 속성이 항상 기대대로 동작하지 않기 때문에, <code class="language-plaintext highlighter-rouge">setTimeout</code>을 이용해 포커스를 설정하는 방식으로 보완할 수 있습니다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">searchField</span><span class="dl">'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
  <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>페이지가 완전히 로드된 후 약간의 지연을 두고 포커스를 설정하는 방식으로, 일부 상황에서는 도움이 될 수 있습니다.</p>

<h3 id="4-입력-안내-메시지-표시">4. 입력 안내 메시지 표시</h3>

<p>페이지가 로드되었을 때, 사용자에게 검색창을 클릭하도록 안내하는 메시지를 표시하는 방법도 고려할 수 있습니다. 이 방법은 사용자가 검색을 시작하도록 유도하는 간단한 방법입니다.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"searchPrompt"</span><span class="nt">&gt;</span>Please tap here to start searching<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"searchField"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>이 방법은 사용자가 직접 검색을 시작하도록 유도함으로써, 강제적인 포커스 설정의 문제를 우회할 수 있습니다.</p>

<h3 id="5-커스텀-버튼으로-입력-필드로-포커스-이동">5. 커스텀 버튼으로 입력 필드로 포커스 이동</h3>

<p>페이지 전환 후 특정 버튼을 눌렀을 때, 검색 필드로 포커스를 이동시키는 방법입니다. 예를 들어 “검색 시작” 버튼을 만들어 이 버튼을 클릭하면 검색창에 포커스가 가게 할 수 있습니다.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"focusButton"</span><span class="nt">&gt;</span>Start Search<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"searchField"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;script&gt;</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">focusButton</span><span class="dl">'</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">searchField</span><span class="dl">'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
  <span class="p">});</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>이 방법은 사용자에게 명확한 동작을 제시하여, 직접적인 클릭을 유도함으로써 포커스를 설정할 수 있게 합니다.</p>

<h3 id="결론">결론</h3>

<p>iOS에서는 사용자의 직접적인 터치 없이 자동으로 <code class="language-plaintext highlighter-rouge">input</code>에 포커스를 주는 것이 제한될 수 있습니다. 하지만 사용자 경험을 고려한 다양한 방법들을 통해 이 문제를 해결할 수 있습니다. 페이지 전환을 피하거나, 사용자의 직접적인 인터랙션을 유도함으로써 원하는 동작을 유도하는 것이 핵심입니다.</p>]]></content><author><name>mooooburg-dev</name><email>jeongmupark@gmail.com</email></author><category term="ios" /><category term="input" /><category term="focus" /><summary type="html"><![CDATA[아이폰(iOS)에서 Input 자동 포커스 문제를 해결하는 방법과 UX 개선 팁]]></summary></entry></feed>