<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>My Blog</title>
        <link>https://fixedblog-3lz.pages.dev</link>
        <description>새로운 블로그에 오신 것을 환영합니다. (설정에서 이 문구를 수정하세요.)</description>
        <language>ko</language>
        <atom:link href="https://fixedblog-3lz.pages.dev/rss.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Thu, 03 Sep 2026 04:03:07 GMT</lastBuildDate>
        <item>
            <title><![CDATA[SKBE v1.0.0.10 업데이트: 포스트 언어별 탭, 허브 연동 보완 및 에디터 개선]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/release-v1-0-0-10-post-management-tabs-and-hub-sync</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/release-v1-0-0-10-post-management-tabs-and-hub-sync</guid>
            <pubDate>Wed, 26 Aug 2026 06:12:14 GMT</pubDate>
            <description><![CDATA[다국어 글이 늘어남에 따라 포스트 관리에서 언어별로 모아볼 수 있는 탭 필터를 추가하고, 블로그 허브 연동 안정성 및 에디터 서식 유실 방지 처리를 보완한 v1.0.0.10 릴리즈 내용입니다.]]></description>
            <content:encoded><![CDATA[<h1 id="스벨트킷-블로그-엔진-v10010-업데이트-안내">스벨트킷 블로그 엔진 v1.0.0.10 업데이트 안내</h1>
<p>스벨트킷 블로그 엔진(SvelteKit Blog Engine) v1.0.0.10 버전 업데이트 내용입니다.</p>
<p>다국어로 글을 계속 작성하다 보니 관리자 페이지에서 특정 언어의 글만 따로 모아서 보거나, 어떤 언어의 번역이 덜 작성되었는지 확인하기 불편한 점이 있었습니다. 이번 버전에서는 이 부분을 개선하기 위해 <strong>포스트 관리 페이지의 동적 언어 탭</strong>을 추가하고, <strong>블로그 허브 연동 프로토콜 보완</strong>, <strong>에디터 서식 유실 방지</strong>, <strong>배포 명령어 간소화</strong> 작업을 진행했습니다.</p>
<hr>
<h2 id="1-포스트-관리-페이지-언어별-필터-탭-추가">1. 포스트 관리 페이지 언어별 필터 탭 추가</h2>
<p>기존에는 전체 포스트가 작성일 순으로만 나열되어 있어, 한국어, 영어, 일본어 등 여러 언어가 섞여 있을 때 관리하기가 번거로웠습니다.<br><img src="https://sveltekitblog.com/images/posts/release-v1-0-0-10-post-management-tabs-and-hub-sync/desktop/img-devlog-release-v1-0-0-10-post-management-tabs-and-hub-sync-ko-002.webp" alt="img-devlog-release-v1-0-0-10-post-management-tabs-and-hub-sync-ko-002"></p>
<h3 id="주요-변경-사항">주요 변경 사항</h3>
<ul>
<li><strong>동적 언어 감지 및 카운트 배지</strong>: 코드로 언어 목록을 고정하지 않고, 데이터베이스에 등록된 언어와 실제 작성된 포스트 데이터를 종합하여 탭을 동적으로 만듭니다. 각 탭 옆에는 작성된 글 개수가 실시간으로 표시됩니다.</li>
<li><strong>글이 없는 언어 선택 시 안내</strong>: 아직 작성된 글이 없는 언어 탭(예: <code>English (0)</code>)을 누르면 단순 빈 화면 대신 안내 문구와 함께 해당 언어로 바로 글을 작성할 수 있는 링크 버튼이 표시됩니다.</li>
<li><strong>단일 언어 환경 고려</strong>: 다국어를 쓰지 않고 단 1개의 언어로만 운영되는 블로그에서는 불필요하게 탭 바가 나오지 않도록 처리했습니다.</li>
<li><strong>목록 내 언어 뱃지 및 페이지네이션 연동</strong>: 테이블의 각 제목 옆에 언어 코드(<code>KR</code>, <code>EN</code>, <code>JA</code> 등)를 표시하여 한눈에 구분할 수 있게 했고, 탭을 바꿀 때 자동으로 1페이지로 넘어가도록 처리했습니다.</li>
</ul>
<hr>
<h2 id="2-블로그-허브hub-연동-프로토콜-보완">2. 블로그 허브(Hub) 연동 프로토콜 보완</h2>
<p>중앙 피드 플랫폼인 스벨트킷 블로그 허브(<code>hub.sveltekitblog.com</code>)로 글을 전송할 때 발생하던 자잘한 엣지 케이스들을 정리했습니다.</p>
<h3 id="주요-보완-내용">주요 보완 내용</h3>
<ul>
<li><strong>서론 1,500자 HTML 자동 추출</strong>: 마크다운이든 비주얼 HTML이든, 피드 카드에 적합한 서론 1,500자 분량의 HTML을 서버 측에서 정제하여 허브로 전달합니다.</li>
<li><strong>이미지 상대경로 자동 변환</strong>: 본문에 들어간 <code>/images/posts/...</code> 같은 내부 상대 경로 이미지를 사이트 대표 도메인이 포함된 완전한 절대 URL(<code>https://도메인/images/...</code>)로 자동 변환하여, 허브 피드에서 썸네일과 본문 이미지가 깨지지 않도록 했습니다.</li>
<li><strong>글 수정 시 최초 발행일 유지</strong>: 이미 올라간 글을 수정할 때 수정일시로 덮어써지지 않고, 최초 발행일(<code>published_at</code>)을 보존하여 허브 피드 타임라인의 시간 순서가 꼬이지 않도록 수정했습니다.</li>
<li><strong>허브 자동 제출 기본값 설정</strong>: 관리자 설정(<code>⚙️ 설정</code>)에 &#39;글 작성 시 허브 자동 제출 기본 활성화&#39; 토글을 추가하여, 새 글을 쓸 때 매번 체크박스를 따로 누르지 않아도 되도록 개선했습니다.</li>
</ul>
<hr>
<h2 id="3-포스트-에디터-서식-유실-방지-가드">3. 포스트 에디터 서식 유실 방지 가드</h2>
<p>포스트 에디터에서 모드를 전환하거나 에디터 종류를 바꿀 때 작성 중이던 내용이 실수로 날아가는 일을 방지하는 안전장치를 추가했습니다.</p>
<ul>
<li><strong>HTML ➔ Visual 모드 전환 경고</strong>: HTML 에디터에서 커스텀 테이블(표)이나 인라인 스타일을 작성한 뒤 Visual 모드로 돌아갈 경우, 티프탭(Tiptap) 파서에 의해 비표준 태그가 정제되어 지워질 수 있습니다. 이런 손실을 막기 위해 모드 전환 전 확인 알림창을 띄우도록 했습니다.</li>
<li><strong>에디터 유형(HTML ↔ Markdown) 변경 락</strong>: 이미 저장된 글의 경우 데이터 보호를 위해 에디터 종류를 잠그고, 변경을 시도할 때 경고창을 통해 데이터 손실을 사전에 인지할 수 있도록 했습니다.</li>
</ul>
<hr>
<h2 id="4-다중-계정-배포deploy-multi-cli-옵션-간소화">4. 다중 계정 배포(deploy-multi) CLI 옵션 간소화</h2>
<p>여러 계정을 배포할 때 사용하는 <code>deploy-multi</code> 스크립트의 인자 입력 방식을 직관적으로 바꿨습니다.</p>
<pre><code class="language-bash"># 이전 방식
npm run deploy:multi -- myaccount --admin-only

# 변경된 방식 (간결한 옵션 지원)
npm run deploy:multi -- myaccount admin   # 어드민만 배포
npm run deploy:multi -- myaccount blog    # 블로그만 배포
npm run deploy:multi -- myaccount         # 전체(블로그+어드민) 배포
</code></pre>
<p>기존에 사용하던 <code>--admin-only</code>, <code>--blog-only</code> 플래그도 그대로 작동합니다.</p>
<hr>
<h2 id="적용-방법">적용 방법</h2>
<p>저장소에서 최신 코드를 pull 받은 뒤 배포를 진행하시면 됩니다.</p>
<pre><code class="language-bash"># 최신 코드 받기
git pull origin main

# 배포 실행 (예시)
npm run deploy:admin
# 또는 멀티 배포 시
npm run deploy:multi -- &lt;계정명&gt; admin
</code></pre>
<p>블로그를 직접 운영하면서 필요한 기능이나 수정할 점이 보이면 계속해서 다듬어나갈 예정입니다. 감사합니다.</p>
]]></content:encoded>
            <category>개발 일지</category>
            <enclosure url="https://sveltekitblog.com/images/posts/release-v1-0-0-10-post-management-tabs-and-hub-sync/desktop/img-devlog-release-v1-0-0-10-post-management-tabs-and-hub-sync-ko-002.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[스벨트킷 블로그 허브(Hub) 연동 및 자동 발행 완벽 가이드]]></title>
            <link>https://fixedblog-3lz.pages.dev/detail-manual/blog-hub-integration-manual</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/detail-manual/blog-hub-integration-manual</guid>
            <pubDate>Tue, 25 Aug 2026 22:28:22 GMT</pubDate>
            <description><![CDATA[스벨트킷 블로그 허브(hub.sveltekitblog.com)와 내 독립 블로그를 연동하여 트래픽을 확장하고, 글 작성·수정·삭제 라이프사이클을 안전하게 자동화하는 완벽 가이드입니다.]]></description>
            <content:encoded><![CDATA[<h1 id="스벨트킷-블로그-허브hub-연동-및-자동-발행-완벽-가이드">🌐 스벨트킷 블로그 허브(Hub) 연동 및 자동 발행 완벽 가이드</h1>
<p>독립형 개인 블로그를 운영할 때 가장 큰 고민은 **&quot;어떻게 새로운 독자에게 내 글을 알릴 것인가?&quot;**입니다. 상용 플랫폼과 달리 독립 블로그는 데이터와 디자인의 완전한 자유를 누릴 수 있지만, 초기 유입과 검색 노출에는 많은 시간과 노력이 필요합니다.</p>
<p>**<a href="https://hub.sveltekitblog.com/">스벨트킷 블로그 통합 허브(hub.sveltekitblog.com)</a>**는 이러한 독립 블로그들의 발견성(Discovery) 한계를 극복하고, 블로거들이 함께 트래픽과 통계를 나누며 성장할 수 있도록 설계된 분산 연합 피드 플랫폼입니다.</p>
<p>본 가이드에서는 <strong>API Key 발급부터 블로그 관리자 연동, 글 작성·수정·삭제 라이프사이클 및 애드센스 시너지</strong>까지 허브 연동의 모든 과정을 상세히 안내합니다.</p>
<hr>
<h2 id="1-허브-연동의-핵심-특징-및-원리">1. 🎯 허브 연동의 핵심 특징 및 원리</h2>
<p><img src="https://sveltekitblog.com/images/posts/blog-hub-integration-manual/desktop/img-detail-manual-blog-hub-integration-manual-ko-001.webp" alt="img-detail-manual-blog-hub-integration-manual-ko-001"></p>
<ol>
<li><strong>데이터의 완전한 독립성 &amp; 소유권</strong>:<ul>
<li>글의 원본 본문과 모든 DB 데이터는 내 Cloudflare D1에 온전히 보관됩니다.</li>
<li>허브에는 카드 노출을 위한 **표준 HTML 서론(1,000~1,500자)**과 메타데이터(제목, 썸네일, 슬러그 등)만 안전하게 전송됩니다.</li>
</ul>
</li>
<li><strong>이미지 절대 경로 자동 변환</strong>:<ul>
<li>마크다운이나 HTML에 포함된 상대 경로 이미지(<code>/images/...</code>)는 내 블로그의 도메인을 포함한 절대 URL(<code>https://...</code>)로 자동 변환되어 허브 피드에 깨짐 없이 선명하게 표시됩니다.</li>
</ul>
</li>
<li><strong>비동기 논블로킹(Non-blocking) 장애 격리</strong>:<ul>
<li>허브 서버의 네트워크 지연이나 일시적 오류가 발생하더라도, <strong>내 블로그의 글 저장 트랜잭션은 100% 정상 완료</strong>되도록 안전하게 보호됩니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="2-1단계-허브-api-key-발급-및-블로그-설정">2. 🔑 1단계: 허브 API Key 발급 및 블로그 설정</h2>
<h3 id="허브-플랫폼에서-api-key-발급">① 허브 플랫폼에서 API Key 발급</h3>
<p><img src="https://sveltekitblog.com/images/posts/blog-hub-integration-manual/desktop/img-detail-manual-blog-hub-integration-manual-ko-002.webp" alt="img-detail-manual-blog-hub-integration-manual-ko-002"></p>
<ol>
<li>**<a href="https://hub.sveltekitblog.com/register-site">스벨트킷 블로그 허브의 &#39;내 블로그 연동 (API 발급)&#39;</a>**에 접속합니다.</li>
<li>내 블로그 대표 URL(예: <code>https://myblog.com</code>)을 등록하고, 고유 발급된 <strong>Site API Key</strong>(<code>sk_board_...</code> 또는 <code>sk_hub_...</code>)를 복사합니다.</li>
</ol>
<h3 id="블로그-관리자-페이지-설정-등록">② 블로그 관리자 페이지(<code>⚙️ 설정</code>) 등록</h3>
<ol>
<li>내 블로그 관리자 페이지 접속 후 <strong>[설정]</strong> 메뉴로 이동합니다.</li>
<li><strong>스벨트킷블로그 허브(Hub) 연동 설정</strong> 섹션을 찾습니다.<br><img src="https://sveltekitblog.com/images/posts/blog-hub-integration-manual/desktop/img-detail-manual-blog-hub-integration-manual-ko-003.webp" alt="img-detail-manual-blog-hub-integration-manual-ko-003"></li>
</ol>
<pre><code class="language-text">[ 스벨트킷블로그 허브(Hub) 연동 설정 ]
* 허브 플랫폼 URL : https://hub.sveltekitblog.com (고정)
* 허브 발급 API Key : sk_hub_live_... (발급받은 키 입력)
* 🌐 글 작성 시 허브 동시 발행 기본 활성화 : [ON / OFF 토글]
</code></pre>
<ol start="3">
<li><code>글 작성 시 허브 동시 발행 기본 활성화</code>를 켜두시면 새 글을 작성할 때마다 자동으로 연동 체크박스가 활성화됩니다.</li>
<li>하단의 <strong>[설정 저장하기]</strong> 버튼을 눌러 설정을 완료합니다.</li>
</ol>
<hr>
<h2 id="3-2단계-포스트-에디터에서-허브-동시-발행하기">3. ✍️ 2단계: 포스트 에디터에서 허브 동시 발행하기</h2>
<p>글 작성 및 수정 화면(<code>새 글 작성</code> 또는 <code>글 수정</code>)의 메타데이터 사이드바 하단에 <strong>허브 자동 제출 체크박스</strong>가 제공됩니다.<br><img src="https://sveltekitblog.com/images/posts/blog-hub-integration-manual/desktop/img-detail-manual-blog-hub-integration-manual-ko-004.webp" alt="img-detail-manual-blog-hub-integration-manual-ko-004"></p>
<pre><code class="language-text">[☑️] 🌐 스벨트킷블로그 허브(hub.sveltekitblog.com)에 자동 제출
     체크 시 글이 발행될 때 허브 피드로 요약 카드가 자동 전송됩니다.
     체크를 해제하면 허브 피드에서 &quot;숨김&quot; 처리되어 추천(좋아요) 통계가 안전하게 보존되며,
     허브에서 완전히 삭제하려면 블로그에서 포스트를 삭제해야 합니다.
</code></pre>
<ul>
<li><strong>공개 글 발행 (<code>Status: Published</code>)</strong>:<ul>
<li>체크박스가 켜진 상태로 글을 저장하면, D1 DB 저장과 동시에 허브로 표준 HTML 서론과 썸네일이 전송되어 허브 피드에 즉시 노출됩니다.</li>
</ul>
</li>
<li><strong>임시저장 (<code>Status: Draft</code>)</strong>:<ul>
<li>상태가 임시저장인 글은 체크박스를 켜두더라도 허브 피드에 공개되지 않습니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-3단계-글-수정-url-변경-비공개-삭제-라이프사이클">4. 🔄 3단계: 글 수정, URL 변경, 비공개, 삭제 라이프사이클</h2>
<p>블로그를 운영하다 보면 글의 내용을 보강하거나, 카테고리를 변경하고, 글을 비공개하거나 삭제하는 상황이 발생합니다. 블로그 엔진은 이러한 상황을 자동으로 감지하여 허브와 완벽하게 동기화합니다.</p>
<table>
<thead>
<tr>
<th align="left">상황</th>
<th align="left">블로그 동작</th>
<th align="left">허브(Hub) 동기화 동작</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>글 내용 단순 수정</strong></td>
<td align="left">본문 수정 후 저장</td>
<td align="left"><strong>최초 발행일(<code>published_at</code>)을 보존</strong>한 상태로 카드 내용 및 썸네일만 최신화 (피드 상단으로 불필요하게 튀지 않음)</td>
</tr>
<tr>
<td align="left"><strong>슬러그 / 카테고리 변경</strong></td>
<td align="left">URL 변경 후 저장</td>
<td align="left">허브에 등록되어 있던 **이전 URL 카드를 자동 삭제(<code>DELETE</code>)**하고, <strong>새 URL로 신규 등록(<code>POST</code>)</strong> (깨진 404 링크 방지)</td>
</tr>
<tr>
<td align="left"><strong>비공개 전환 / 체크 해제</strong></td>
<td align="left">Draft 전환 또는 체크 해제</td>
<td align="left">허브 피드에서 <strong>숨김(<code>hidden</code>) 처리</strong> (독자들이 남긴 <strong>추천/좋아요 통계는 영구 보존</strong>)</td>
</tr>
<tr>
<td align="left"><strong>블로그에서 포스트 삭제</strong></td>
<td align="left">관리자에서 글 완전 삭제</td>
<td align="left">허브에서도 <strong>해당 포스트 카드를 완전 삭제(<code>DELETE</code>)</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="5-구글-애드센스-승인-준비-및-시너지-효과">5. 💰 구글 애드센스 승인 준비 및 시너지 효과</h2>
<p>허브 연동을 통해 유입되는 트래픽은 구글 애드센스 승인 심사 및 수익 창출에 매우 긍정적인 영향을 줍니다.</p>
<ol>
<li><strong>초기 유효 트래픽 확보</strong>:<ul>
<li>구글 봇은 방문자가 전혀 없는 블로그보다 실제 활성 독자가 유입되는 사이트를 훨씬 빠르게 크롤링하고 높게 평가합니다.</li>
</ul>
</li>
<li><strong><code>ads.txt</code> 완벽 자동 서빙</strong>:<ul>
<li>관리자 설정의 <code>ads_txt</code> 입력창에 애드센스 승인 코드(<code>google.com, pub-..., DIRECT, f08c47fec0942fa0</code>)를 넣어두면 <code>https://내도메인.com/ads.txt</code>로 즉시 서빙됩니다.</li>
</ul>
</li>
<li><strong>콘텐츠 품질 가이드</strong>:<ul>
<li>애드센스 1차 승인을 위해 <strong>1,500자 이상의 양질의 IT/기술/정보성 글을 15~20편 이상</strong> 꾸준히 허브와 함께 발행하시는 것을 권장합니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="6-마치며">6. 🏁 마치며</h2>
<p>스벨트킷 블로그 허브 연동을 통해 나만의 독립 블로그가 가진 데이터 통제권을 온전히 지키면서, 더 많은 독자들과 연결되는 강력한 시너지를 경험해 보세요!</p>
]]></content:encoded>
            <category>상세 메뉴얼</category>
            <enclosure url="https://sveltekitblog.com/images/posts/blog-hub-integration-manual/desktop/img-detail-manual-blog-hub-integration-manual-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[새 릴리즈 코드를 가장 안전하게 올리는 법: Cloudflare Pages 다계정 배포 자동화]]></title>
            <link>https://fixedblog-3lz.pages.dev/general-guide/cloudflare-pages-multi-account-deploy-guide</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/general-guide/cloudflare-pages-multi-account-deploy-guide</guid>
            <pubDate>Sun, 23 Aug 2026 13:12:22 GMT</pubDate>
            <description><![CDATA[새로운 릴리즈 코드가 나왔을 때 테스트 계정에서 먼저 검증하고 본 계정에 안전하게 반영하는 방법! 캐시 충돌 없는 Cloudflare Pages 원클릭 다계정 배포 파이프라인을 소개합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="새-릴리즈-코드를-가장-안전하게-올리는-법-cloudflare-pages-다계정-배포-자동화">🚀 새 릴리즈 코드를 가장 안전하게 올리는 법: Cloudflare Pages 다계정 배포 자동화</h1>
<p>깃허브 저장소에 새로운 릴리즈 버전이 올라왔을 때, 최신 코드를 내려받아 내 블로그에 배포하다 보면 한 가지 조심스러운 고민이 생깁니다.</p>
<blockquote>
<p><strong>&quot;새 릴리즈의 엔진 코어와 데이터베이스 마이그레이션이 실제 운영 중인 내 본 계정 블로그에 문제없이 잘 적용될까?&quot;</strong></p>
</blockquote>
<p>스벨트킷 블로그 엔진은 레이아웃, 컬러, 폰트 같은 디자인 설정과 작성한 글이 모두 <strong>D1 데이터베이스에 안전하게 보존</strong>되므로, 코드를 업데이트하더라도 어드민에서 꾸며둔 블로그 UI가 초기화되지는 않습니다.</p>
<p>하지만 새 버전에서 추가된 코어 기능이나 D1 스키마 변경, 배포 스크립트가 내 Cloudflare 환경에 정상적으로 잘 배포되는지 사전에 확인해보고 싶을 때가 있습니다. 이때 가장 안전한 방법은 <strong>무료 Cloudflare 보조 계정(테스트 인스턴스)에 먼저 배포해 본 뒤, 정상 가동을 확인하고 본 계정에 올리는 것</strong>입니다.</p>
<p>기존에는 Cloudflare 기본 도구(Wrangler)로 여러 계정을 오가려면 매번 브라우저 로그인을 다시 하거나 D1/KV ID를 일일이 수동 교체해야 했고, 로컬 캐시가 엉켜 배포가 꼬이기도 했습니다. 이번 글에서는 이러한 번거로움 없이 **새 릴리즈 코드를 테스트 계정에서 먼저 검증하고 본 계정으로 안전하게 배포하는 &#39;다계정 원클릭 배포 파이프라인&#39;**을 정리해 보았습니다.</p>
<hr>
<h2 id="1-실전-안전-배포-워크플로우">🔄 1. 실전 안전 배포 워크플로우</h2>
<p>다계정 배포 시스템을 활용하면 다음과 같이 안전한 배포 사이클을 구축할 수 있습니다:<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-multi-account-deploy-guide/desktop/img-general-guide-cloudflare-pages-multi-account-deploy-guide-ko-001.webp" alt="img-general-guide-cloudflare-pages-multi-account-deploy-guide-ko-001"></p>
<hr>
<h2 id="2-준비물-cloudflare-api-토큰-발급-계정당-최초-1회">🔑 2. 준비물: Cloudflare API 토큰 발급 (계정당 최초 1회)</h2>
<p>브라우저 로그인 팝업 없이 터미널에서 백그라운드로 안전하게 배포하기 위해, 각 Cloudflare 계정에서 <strong>API 토큰</strong>을 딱 한 번만 발급받아 둡니다.</p>
<h3 id="2-1-안전한-최소-권한-api-토큰-만들기">2-1. 안전한 최소 권한 API 토큰 만들기</h3>
<ol>
<li><a href="https://dash.cloudflare.com">Cloudflare 대시보드</a> 로그인 ➔ 우측 상단 <strong>[내 프로필(My Profile)]</strong> ➔ <strong>[API 토큰(API Tokens)]</strong> 메뉴로 이동합니다.</li>
<li>**[토큰 만들기(Create Token)]**를 누르고, 목록에서 <strong><code>Cloudflare Pages 편집(Edit Cloudflare Pages)</code></strong> 템플릿의 **[템플릿 사용]**을 클릭합니다.</li>
<li><strong>권한(Permissions)</strong> 섹션 하단의 **<code>+ 더 추가(+ Add more)</code>**를 눌러 다음 2가지 권한을 추가합니다:<ul>
<li><strong><code>계정(Account)</code></strong> - <strong><code>D1</code></strong> - <strong><code>편집(Edit)</code></strong> <em>(D1 데이터베이스 스키마 자동 동기화용)</em></li>
<li><strong><code>계정(Account)</code></strong> - <strong><code>Workers KV Storage</code></strong> - <strong><code>편집(Edit)</code></strong> <em>(이미지 저장소 바인딩용)</em></li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>💡 보안 팁</strong>: 모든 권한을 다 주는 전역 키 대신, 블로그 빌드와 배포에 꼭 필요한 **Pages, D1, KV 3개 리소스만 권한을 주는 방식(최소 권한 원칙)**이 가장 안전합니다.</p>
</blockquote>
<ol start="4">
<li>**계정 리소스(Account Resources)**에서 해당 계정을 선택하고, **[계속해서 요약 보기] ➔ [토큰 만들기]**를 누릅니다.</li>
<li>발급된 <strong>API 토큰 문자열</strong>을 복사해 둡니다.</li>
</ol>
<h3 id="2-2-32자리-계정-id-확인">2-2. 32자리 계정 ID 확인</h3>
<p>대시보드 접속 후 브라우저 주소창(URL)에서 32자리 문자열을 복사합니다:</p>
<pre><code class="language-text">https://dash.cloudflare.com/1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d/workers-and-pages
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                             (이 부분이 Account ID입니다)
</code></pre>
<hr>
<h2 id="3-백업-파일로-1초-만에-계정-등록하기-deploysync">📦 3. 백업 파일로 1초 만에 계정 등록하기 (<code>deploy:sync</code>)</h2>
<p>새 계정의 D1 ID, KV ID, 프로젝트명을 복잡하게 손으로 적을 필요 없이, <strong>어드민의 기본 기능인 &#39;배포 설정 백업&#39; 파일</strong>을 이용해 한 번에 등록합니다.</p>
<ol>
<li>배포 대상 블로그의 어드민 페이지 ➔ **[환경설정] ➔ [데이터 관리]**에서 **[배포 설정 다운로드]**를 클릭합니다.</li>
<li>다운로드된 파일 이름을 계정 식별자로 변경하여 프로젝트 루트 폴더에 넣습니다:<ul>
<li>예: 테스트 계정 백업 파일 ➔ <code>wrangler.backup.test.json</code></li>
<li>예: 본 계정 백업 파일 ➔ <code>wrangler.backup.main.json</code></li>
</ul>
</li>
<li>터미널에서 동기화 명령을 실행합니다:</li>
</ol>
<pre><code class="language-bash">npm run deploy:sync
</code></pre>
<pre><code class="language-text">======================================================
✅ [.deploy-accounts.json] 계정 설정 동기화 완료!
======================================================
📋 [현재 등록된 계정 목록]
  - main (Main Blog Account): 🟢 준비 완료
  - test (Test Blog Account): 🟡 token/accountId 입력 필요
</code></pre>
<ol start="4">
<li>생성된 <code>.deploy-accounts.json</code> 파일을 열고, 발급받은 <code>token</code>과 <code>accountId</code>를 입력해 줍니다:</li>
</ol>
<pre><code class="language-json">{
  &quot;test&quot;: {
    &quot;name&quot;: &quot;Test Staging Account&quot;,
    &quot;token&quot;: &quot;실제_발급받은_API_토큰&quot;,
    &quot;accountId&quot;: &quot;실제_32자리_계정_ID&quot;,
    &quot;blogProject&quot;: &quot;test-blog-web&quot;,
    &quot;adminProject&quot;: &quot;test-blog-admin&quot;,
    &quot;d1&quot;: {
      &quot;BLOG_DB&quot;: { &quot;name&quot;: &quot;test-blog-db&quot;, &quot;id&quot;: &quot;11111111-2222-3333-4444-555555555555&quot; },
      &quot;USER_DB&quot;: { &quot;name&quot;: &quot;test-user-db&quot;, &quot;id&quot;: &quot;66666666-7777-8888-9999-000000000000&quot; }
    },
    &quot;kv&quot;: {
      &quot;IMAGES_KV&quot;: &quot;aaaaaaaaaabbbbbbbbbbccccccccccdd&quot;
    }
  }
}
</code></pre>
<blockquote>
<p><strong>안전 보장</strong>: <code>deploy:sync</code>는 이미 입력해 둔 토큰과 계정 ID를 절대 지우지 않고 안전하게 보존(Merge)합니다.</p>
</blockquote>
<hr>
<h2 id="4-릴리즈-업데이트-실전-배포-시나리오">🚀 4. 릴리즈 업데이트 실전 배포 시나리오</h2>
<p>이제 새 릴리즈가 떴을 때 터미널에서 명령어 한 줄로 안전하게 배포할 수 있습니다.</p>
<h3 id="시나리오-a-릴리즈-테스트-계정에-먼저-쏴보기-권장">시나리오 A. 릴리즈 테스트 계정에 먼저 쏴보기 (권장)</h3>
<p>새 버전의 엔진 코어와 마이그레이션을 테스트 계정에 먼저 올려서 Blog(웹)와 Admin(관리자)이 정상 가동되는지 확인합니다.</p>
<pre><code class="language-bash">npm run deploy:multi -- test
</code></pre>
<h3 id="시나리오-b-검증-완료-후-본-계정에-안전-배포">시나리오 B. 검증 완료 후 본 계정에 안전 배포</h3>
<p>테스트 인스턴스에서 새 기능과 글 작성 등이 정상 작동하는 것을 확인했다면 본 계정에 배포합니다.</p>
<pre><code class="language-bash">npm run deploy:multi -- main
</code></pre>
<h3 id="시나리오-c-여러-개의-블로그를-한-번에-일괄-판올림">시나리오 C. 여러 개의 블로그를 한 번에 일괄 판올림</h3>
<p>메인 블로그 외에 서브 블로그를 함께 운영하고 있다면, 새 릴리즈 코드를 등록된 모든 계정에 한 번에 일괄 배포할 수 있습니다.</p>
<pre><code class="language-bash">npm run deploy:multi -- --all
</code></pre>
<h3 id="시나리오-d-블로그-웹-또는-관리자-앱만-선별-배포">시나리오 D. 블로그 웹 또는 관리자 앱만 선별 배포</h3>
<p>블로그 프론트엔드 코드만 수정되었거나 어드민 기능만 수정되었을 때는 필요한 앱만 골라서 배포할 수 있습니다.</p>
<pre><code class="language-bash"># test 계정의 Blog(웹)만 배포
npm run deploy:multi -- test --blog-only

# test 계정의 Admin(관리자)만 배포
npm run deploy:multi -- test --admin-only
</code></pre>
<hr>
<h2 id="5-계정-간-충돌을-원천-차단하는-3단계-샌드박스-설계">🛡️ 5. 계정 간 충돌을 원천 차단하는 3단계 샌드박스 설계</h2>
<p>여러 Cloudflare 계정을 오가며 배포할 때 가장 위험한 문제는 <strong>로컬 캐시 엉킴으로 인해 엉뚱한 계정에 사이트가 배포되는 사고</strong>입니다. Wrangler는 로컬 PC에 이전 계정 세션을 캐시해 두는 습성이 있기 때문입니다.</p>
<p>이를 원천 차단하기 위해 배포 러너에는 <strong>3단계 샌드박스 격리 시스템</strong>이 적용되어 있습니다:<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-multi-account-deploy-guide/desktop/img-general-guide-cloudflare-pages-multi-account-deploy-guide-ko-002.webp" alt="img-general-guide-cloudflare-pages-multi-account-deploy-guide-ko-002"></p>
<ol>
<li><strong>임시 세션 샌드박스</strong>: 배포가 진행되는 동안 <code>APPDATA</code>를 임시 디렉토리로 격리하여, 내 컴퓨터의 기존 브라우저 로그인 세션에 영향을 주지 않습니다.</li>
<li><strong>원자적(In-Memory) 설정 복구</strong>: 배포 순간에만 해당 계정의 <code>wrangler.json</code>을 적용하고, 배포가 끝나면 <code>finally</code> 블록에서 기존 파일로 100% 원래대로 복구합니다.</li>
<li><strong>로컬 캐시 자동 청소</strong>: 배포 직후 로컬 캐시를 비워주므로, 평소에 쓰시던 단일 배포(<code>npm run deploy:blog</code>, <code>deploy:admin</code>)와 다계정 배포를 번갈아 실행해도 세션이 꼬이지 않습니다.</li>
</ol>
<hr>
<h2 id="6-주요-명령어-요약">📋 6. 주요 명령어 요약</h2>
<table>
<thead>
<tr>
<th align="left">용도</th>
<th align="left">명령어</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>계정 설정 자동 동기화</strong></td>
<td align="left"><code>npm run deploy:sync</code></td>
</tr>
<tr>
<td align="left"><strong>특정 계정 배포 (Blog + Admin)</strong></td>
<td align="left"><code>npm run deploy:multi -- &lt;계정명&gt;</code></td>
</tr>
<tr>
<td align="left"><strong>특정 계정 Blog만 선별 배포</strong></td>
<td align="left"><code>npm run deploy:multi -- &lt;계정명&gt; --blog-only</code></td>
</tr>
<tr>
<td align="left"><strong>특정 계정 Admin만 선별 배포</strong></td>
<td align="left"><code>npm run deploy:multi -- &lt;계정명&gt; --admin-only</code></td>
</tr>
<tr>
<td align="left"><strong>등록된 모든 계정 일괄 판올림</strong></td>
<td align="left"><code>npm run deploy:multi -- --all</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="7-마치며">💡 7. 마치며</h2>
<p>어드민에서 직접 꾸며둔 디자인 설정과 소중한 글 데이터는 D1 데이터베이스에 그대로 보존되면서, 새로운 엔진 릴리즈 버전이 나올 때마다 테스트 인스턴스를 통해 안심하고 업데이트할 수 있는 배포 파이프라인이 완성되었습니다.</p>
<p>테스트 계정을 활용한 안전 배포 워크플로우로 나만의 블로그를 더욱 안정적이고 편안하게 운영해 보시기 바랍니다.</p>
]]></content:encoded>
            <category>통합 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/cloudflare-pages-multi-account-deploy-guide/desktop/img-general-guide-cloudflare-pages-multi-account-deploy-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[[개발 일지] 스벨트킷 블로그 엔진 v1.0.0.9 릴리즈 및 주요 변경 사항]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/devlog-release-v1009</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/devlog-release-v1009</guid>
            <pubDate>Sun, 23 Aug 2026 12:48:52 GMT</pubDate>
            <description><![CDATA[스벨트킷 블로그 엔진 v1.0.0.9 릴리즈! 다계정 원클릭 Cloudflare Pages 배포, 스벨트킷 블로그 허브 피드 연동, 구글 애드센스 승인을 위한 SEO 전면 최적화 및 i18n 사전 동기화 개선 사항을 소개합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="스벨트킷-블로그-엔진-v1009-릴리즈">🚀 스벨트킷 블로그 엔진 v1.0.0.9 릴리즈</h1>
<p>v1.0.0.8에 이어 진행된 이번 v1.0.0.9 릴리즈에서는 <strong>다계정 원클릭 Cloudflare Pages 통합 배포</strong>, <strong><a href="https://hub.sveltekitblog.com/">SvelteKit Blog Hub BLOG FEED 스벨트킷 블로그 통합 피드</a> 연동</strong>, <strong>구글 애드센스 승인을 위한 SEO/사이트맵 전면 최적화 및 RSS/사이트맵 디자인 통일</strong>, 그리고 <strong>다국어(i18n) 사전 완성도 개선 및 배포 동기화 편의성 향상</strong>이 도입되었습니다.</p>
<hr>
<h3 id="1-다계정-스마트-자동-동기화-및-원클릭-cloudflare-pages-배포">1. 🌐 다계정 스마트 자동 동기화 및 원클릭 Cloudflare Pages 배포</h3>
<ul>
<li><strong><code>wrangler.backup.json</code> 기반 스마트 자동 동기화 (<code>npm run deploy:sync</code>)</strong>:<ul>
<li>관리자에서 다운로드한 백업 파일(<code>wrangler.backup*.json</code>)을 루트에 넣고 동기화 명령을 실행하면 D1 ID, KV ID, 프로젝트명을 자동 감지하여 <code>.deploy-accounts.json</code>에 완벽하게 등록합니다.</li>
<li>사용자가 입력해 둔 API 토큰과 계정 ID는 100% 안전하게 보존(Merge)됩니다.</li>
</ul>
</li>
<li><strong>원클릭 다계정 배포 러너 (<code>npm run deploy:multi</code>)</strong>:<ul>
<li><code>npm run deploy:multi -- &lt;계정명&gt;</code> 커맨드 한 번으로 해당 계정의 **Blog + Admin (2개 CFP)**를 연속 자동 배포합니다.</li>
<li><code>--blog-only</code>, <code>--admin-only</code> 선택 배포 및 등록된 모든 계정 일괄 배포(<code>--all</code>)를 지원합니다.</li>
</ul>
</li>
<li><strong>완벽한 캐시 샌드박스 격리 및 자동 청소</strong>:<ul>
<li><code>APPDATA: .wrangler-multi-temp</code> 임시 폴더를 통해 전역 Wrangler 세션을 100% 격리합니다.</li>
<li>배포 순간에만 <code>wrangler.json</code>을 인메모리 스왑하고 배포 완료 후 <code>finally</code> 블록에서 순정 파일로 100% 원복합니다.</li>
<li>배포 직후 로컬 <code>.wrangler</code> 및 <code>node_modules/.cache/wrangler</code> 캐시를 자동 삭제하여 기존 단일 배포(<code>deploy:admin</code>, <code>deploy:blog</code>)에 0% 간섭을 보장합니다.</li>
</ul>
</li>
<li><strong>D1 스키마 자동 마이그레이션 (<code>sync-secrets.js</code>)</strong>:<ul>
<li>배포 파이프라인에서 D1 DB에 <code>is_syndicated</code> 컬럼을 안전하게 자동 추가합니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="2-스벨트킷-블로그-허브hub-통합-피드-및-글-연동">2. 📡 스벨트킷 블로그 허브(Hub) 통합 피드 및 글 연동</h3>
<ul>
<li><strong>실시간 허브 클라이언트 모듈 (<code>hub.ts</code>, <code>syndication.ts</code>)</strong>:<ul>
<li>글 발행 시 **<a href="https://hub.sveltekitblog.com/">SvelteKit Blog Hub BLOG FEED 스벨트킷 블로그 통합 피드</a>**로 요약 메타데이터 카드를 실시간 전송하는 연동 파이프라인을 구축했습니다.</li>
</ul>
</li>
<li><strong>관리자 에디터 허브 연동 토글 UI (<code>PostMetadataForm.svelte</code>)</strong>:<ul>
<li>글 작성/수정 시 허브 피드 전송 여부(<code>is_syndicated</code>)를 원클릭으로 켜고 끌 수 있는 토글 체크박스를 추가했습니다.</li>
</ul>
</li>
<li><strong>DB 스키마 확장 (<code>schema-blog-db.sql</code>)</strong>:<ul>
<li><code>posts</code> 테이블에 <code>is_syndicated INTEGER DEFAULT 0</code> 컬럼을 정식 추가했습니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-구글-애드센스-quot가치가-별로-없는-콘텐츠quot-극복-및-seo-전면-최적화">3. 🔍 구글 애드센스 &quot;가치가 별로 없는 콘텐츠&quot; 극복 및 SEO 전면 최적화</h3>
<ul>
<li><strong>순수 포스트 중심 검색 색인 전략</strong>:<ul>
<li>단순 목록성 페이지인 카테고리 모아보기(<code>/[category]</code>), 태그 모아보기(<code>/tags/[tag]</code>), 방명록(<code>/guestbook</code>)에 <code>noindex, follow</code>를 적용하여 Thin Content 감점을 원천 방어하고 크롤러가 내부 링크를 탐색하도록 유도합니다.</li>
<li>고품질 본문 포스트와 사이트 신뢰도에 필수적인 법적/소개 페이지(<code>about</code>, <code>privacy</code>, <code>contact</code> 등 CMS 정적 페이지)는 100% 정상 색인(<code>index, follow</code>)되며, 사이트맵에 0.9 우선순위로 정식 등록됩니다.</li>
</ul>
</li>
<li><strong>사이트맵 &amp; RSS 쿼리 정제 및 통일된 디자인 탑재 (<code>sitemap.xsl</code>)</strong>:<ul>
<li><code>sitemap.xml</code>과 <code>rss.xml</code>의 DB 쿼리에서 CMS 페이지와 슬러그를 배제하고 순수 발행 게시글만 엄격하게 추출합니다.</li>
<li>CMS 정적 페이지는 우선순위 0.9 및 다국어 alternate hreflang을 완벽 지원합니다.</li>
<li><strong>기존 <code>rss.xsl</code>과 디자인 톤앤매너를 100% 동일하게 맞춘 영문 <code>static/sitemap.xsl</code>을 신규 탑재</strong>하여 브라우저 접속 시 반응형 영문 테이블 뷰로 렌더링되도록 구현했습니다.</li>
<li><code>sitemap.xml</code>의 캐시 주기를 <code>public, max-age=3600</code>으로 설정하여 1시간 실시간 갱신을 보장합니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="4-다국어i18n-사전-완성도-개선-및-배포-동기화-편의성-향상">4. 🌐 다국어(i18n) 사전 완성도 개선 및 배포 동기화 편의성 향상</h3>
<ul>
<li><strong>다국어 번역 정확도 개선 및 오타 교정</strong>:<ul>
<li>UI 일관성과 번역 정확도 향상을 위해 다국어 사전을 전수 점검하고 부자연스러운 번역 및 오타를 교정했습니다.</li>
</ul>
</li>
<li><strong>관리자 배포 시 i18n DB 동기화 프롬프트 지원 (<code>deploy:admin</code>)</strong>:<ul>
<li><code>npm run deploy:admin</code> 배포 중 로컬 i18n 사전 변경사항을 D1 데이터베이스(<code>ui_dictionary</code>)에 간편하게 동기화할 수 있도록 대화형 프롬프트를 추가했습니다.</li>
</ul>
</li>
<li><strong>관리자 UI 다국어화 보완</strong>:<ul>
<li>허브 연동 관련 모달 및 설정 안내 문구를 다국어 사전과 연동하여 완전한 다국어 지원을 제공합니다.</li>
</ul>
</li>
</ul>
]]></content:encoded>
            <category>개발 일지</category>
        </item>
        <item>
            <title><![CDATA[[개발 일지] 릴리즈 히스토리 요약 (v1.0.0.4 ~ v1.0.0.8)]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/devlog-releases-v1004-to-v1008</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/devlog-releases-v1004-to-v1008</guid>
            <pubDate>Sun, 23 Aug 2026 12:38:32 GMT</pubDate>
            <description><![CDATA[스벨트킷 블로그 엔진 릴리즈 히스토리 요약 (v1.0.0.4 ~ v1.0.0.8)]]></description>
            <content:encoded><![CDATA[<h3 id="release-v1008">🚀 Release v1.0.0.8</h3>
<ul>
<li><strong>카테고리 위젯 단일화</strong>: 혼란을 유발하던 레거시 <code>category_link</code> 설정을 제거하고 단일 <strong>&#39;카테고리 메뉴&#39; 위젯</strong>으로 일원화.</li>
<li><strong>포스트 수 표기 옵션 (<code>showPostCount</code>)</strong>: 어드민 디자인 에디터에서 카테고리명 옆에 글 개수를 표시/숨김 할 수 있는 토글 체크박스 추가 (예: <code>개발 관련 (5)</code>).</li>
<li><strong>어드민 디자인 실시간 미리보기 개선</strong>: 디자인 에디터에서 최근 글, 인기 글, 카테고리 메뉴 위젯이 실제 데이터와 유사한 플레이스홀더로 정상 프리뷰되도록 수정.</li>
<li><strong>발행 글 기준 스마트 <code>hreflang</code> 필터링 (SEO)</strong>: 실제 발행된 글이 존재하는 언어에 대해서만 <code>hreflang</code> 태그를 생성하여, 빈 다국어 페이지가 구글 애드센스 등에서 가치 없는 페이지(Thin Content)로 오판되는 현상 원천 방어.</li>
</ul>
<hr>
<h3 id="release-v1007">🚀 Release v1.0.0.7</h3>
<ul>
<li><strong>엔진 버전 시스템 정식 도입 (<code>APP_VERSION</code>)</strong>: <code>@sveltekitblog/shared</code> 패키지에 엔진 버전 상수를 추가하고, 어드민 사이드바 및 블로그 푸터에 현재 구동 중인 엔진 버전 뱃지를 표시.</li>
<li><strong>전체 라우트 다국어 메타데이터 완성</strong>: 홈, 카테고리, 태그, 검색, 방명록 등 모든 프론트엔드 라우트에 <code>hreflang</code> 및 표준 정규화(Canonical) URL 주입 완성.</li>
<li><strong>푸터 모바일 가독성 개선</strong>: 모바일 화면에서 버전 뱃지 명도 대비 및 레이아웃 여백 최적화.</li>
</ul>
<hr>
<h3 id="release-v1006">🚀 Release v1.0.0.6</h3>
<ul>
<li><strong>모바일 Core Web Vitals 및 성능 최적화</strong>: 동적 OG 이미지 생성, 구글 폰트 API 캐싱, 정적 리소스 프리로드로 모바일 로딩 성능 개선.</li>
<li><strong>사이트맵 500 에러 해결</strong>: <code>sitemap.xml</code> 생성 로직에서 존재하지 않는 레거시 태그 테이블 쿼리를 제거하여 무중단 사이트맵 발행 보장.</li>
<li><strong>동적 HTML <code>lang</code> 속성 최적화</strong>: 접속 언어 컨텍스트(<code>ko</code>, <code>en</code>, <code>ja</code>)에 따라 HTML 루트 태그의 <code>lang</code> 속성이 정확하게 자동 전환되도록 개선.</li>
<li><strong>다국어 i18n 키 구조 정비</strong>: 언어 뱃지 번역 키를 <code>common.lang.short_*</code> 그룹으로 일원화.</li>
</ul>
<hr>
<h3 id="release-v1005">🚀 Release v1.0.0.5</h3>
<ul>
<li><strong>Svelte 5 룬(Runes) 반응형 경고 해결</strong>: 어드민 컴포넌트 내 <code>$state</code> 반응형 상태 컴파일러 경고 수정.</li>
<li><strong>어드민 기본 진입 라우트 정리</strong>: 어드민 대시보드 진입점 및 피드백 흐름 개선.</li>
<li><strong>표준 URL(Canonical) 정규화</strong>: 슬래시 누락 및 다국어 URL 간의 정규화 주소 불일치 버그 수정.</li>
</ul>
<hr>
<h3 id="release-v1004">🚀 Release v1.0.0.4</h3>
<ul>
<li><strong>네이버 웹마스터 도구 H1 태그 중복 오류 해결</strong>: <code>LayoutRenderer</code>의 모바일/데스크톱 레이아웃 전환 방식을 모바일 퍼스트 SSR로 개선하여 검색엔진 봇이 H1 태그를 중복 수집하지 않도록 조치.</li>
<li><strong>푸터 레이아웃 정렬</strong>: 블로그 푸터의 최대 너비와 테두리 스타일을 헤더와 일치시키고 그림자(Shadow) 렌더링 개선.</li>
<li><strong>저장소 정리</strong>: Git 추적에 잘못 포함되어 있던 임시 <code>.bak</code> 파일 정리.</li>
</ul>
]]></content:encoded>
            <category>개발 일지</category>
        </item>
        <item>
            <title><![CDATA[Cloudflare Pages로 웹사이트를 배포하는 2가지 방법과 기본 원리]]></title>
            <link>https://fixedblog-3lz.pages.dev/general-guide/cloudflare-pages-deployment-guide</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/general-guide/cloudflare-pages-deployment-guide</guid>
            <pubDate>Fri, 21 Aug 2026 23:51:06 GMT</pubDate>
            <description><![CDATA[Cloudflare Pages(CFP)를 이용해 웹사이트를 배포하는 2가지 인증 방식(간편 브라우저 로그인 vs API 토큰)의 차이점과 D1 데이터베이스, Workers KV 저장소의 기본 개념 및 실제 블로그 엔진 적용 구조를 알기 쉽게 정리합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="cloudflare-pages로-웹사이트를-배포하는-2가지-방법과-기본-원리">🚀 Cloudflare Pages로 웹사이트를 배포하는 2가지 방법과 기본 원리</h1>
<p>웹사이트나 블로그를 직접 만들고 나면 &#39;이걸 어떻게 인터넷에 올려서 남들이 접속할 수 있게 만들까?&#39;를 고민하게 됩니다.</p>
<p>예전에는 매달 비용을 내고 리눅스 가상 서버(VPS)를 빌려 웹서버(Nginx)를 설치하고 보안 인증서(SSL)를 설정해야 했지만, 요즘은 **Cloudflare Pages(CFP)**를 이용하면 복잡한 서버 관리 없이 무료로 아주 빠르게 사이트를 배포할 수 있습니다.</p>
<p>이 글에서는 Cloudflare Pages가 무엇인지, 내 컴퓨터에서 사이트를 배포할 때 사용하는 **2가지 방법(브라우저 로그인 방식 vs API 토큰 방식)**은 어떤 차이가 있는지, 그리고 글과 사진을 저장하는 <strong>D1 데이터베이스와 KV 저장소</strong>의 기본 원리를 알기 쉽게 정리해 보았습니다.</p>
<hr>
<h2 id="1-cloudflare-pagescfp란-무엇일까요">📂 1. Cloudflare Pages(CFP)란 무엇일까요?</h2>
<p>쉽게 말해 **&quot;내가 만든 웹사이트 파일들을 전 세계 수백 개 도시에 있는 Cloudflare 컴퓨터에 올려두고, 방문자에게 가장 가까운 곳에서 초고속으로 보여주는 호스팅 서비스&quot;**입니다.</p>
<p>HTML, CSS, 이미지 같은 정적 파일은 물론이고, SvelteKit이나 Next.js처럼 화면을 서버에서 실시간으로 그려주는(SSR) 최신 웹사이트도 척척 동작하게 해줍니다.<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-001.webp" alt="img-general-guide-cloudflare-pages-deployment-guide-ko-001"></p>
<h3 id="왜-가상-서버vps-대신-cloudflare-pages를-쓸까요">왜 가상 서버(VPS) 대신 Cloudflare Pages를 쓸까요?</h3>
<ul>
<li><strong>서버 관리를 전혀 안 해도 됩니다</strong>: 해킹 방지용 리눅스 보안 패치를 하거나 웹서버가 죽었는지 감시할 필요가 없습니다. Cloudflare가 24시간 알아서 안전하게 관리해 줍니다.</li>
<li><strong>로딩 속도가 정말 빠릅니다</strong>: 한국 사용자는 한국에 있는 Cloudflare 센터에서, 미국 사용자는 미국에 있는 센터에서 페이지를 즉시 보내주기 때문에 사이트가 쾌속으로 열립니다.</li>
<li><strong>비용이 거의 들지 않습니다</strong>: 개인 블로그나 일반적인 포트폴리오 사이트 수준의 방문자 트래픽은 무료 요금제(Free Tier) 범위 안에서 넉넉하게 운영할 수 있습니다.</li>
</ul>
<hr>
<h2 id="2-컴퓨터에서-cloudflare로-사이트를-배포하는-2가지-방법">🔑 2. 컴퓨터에서 Cloudflare로 사이트를 배포하는 2가지 방법</h2>
<p>SvelteKit 애플리케이션을 빌드(<code>npm run build</code>)하면 <code>@sveltejs/adapter-cloudflare</code>에 의해 <strong><code>.svelte-kit/cloudflare</code></strong> 디렉토리에 정적 파일과 서버 실행용 <code>_worker.js</code>가 생성됩니다.</p>
<p>이 결과물을 Cloudflare Pages에 배포하는 실제 명령어는 <strong><code>npx wrangler pages deploy .svelte-kit/cloudflare</code></strong> 이며, 이때 계정을 인증하는 방식에 크게 2가지가 있습니다.</p>
<h4 id="방법-1-대화형-브라우저-로그인-배포-흐름-wrangler-기본-방식">[방법 1] 대화형 브라우저 로그인 배포 흐름 (Wrangler 기본 방식)</h4>
<p>프로젝트를 처음 만들고 배포할 때 가장 직관적인 방식입니다. 터미널 명령을 치면 브라우저 창이 열리며 로그인 권한을 승인받습니다.<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-002.webp" alt="img-general-guide-cloudflare-pages-deployment-guide-ko-002"></p>
<pre><code class="language-bash"># SvelteKit 빌드 후 Pages 배포 (대화형 브라우저 로그인)
npm run build
npx wrangler pages deploy .svelte-kit/cloudflare --project-name=my-blog-web --branch=production
</code></pre>
<ul>
<li><strong>어떻게 동작하나요?</strong>: 터미널에서 명령어를 치면 내 컴퓨터의 웹 브라우저가 자동으로 열리며 Cloudflare 로그인 화면이 뜹니다. 화면에서 [Allow(승인)] 버튼만 꾹 눌러주면 내 컴퓨터에 로그인 세션이 저장되면서 사이트 배포가 진행됩니다.</li>
<li><strong>장점</strong>: 토큰을 따로 생성할 필요 없이 마우스 클릭 몇 번이면 끝나기 때문에, <strong>계정 1개로 개인 블로그를 처음 시작할 때 가장 쉽고 직관적입니다.</strong></li>
<li><strong>아쉬운 점</strong>:<ul>
<li>Cloudflare 계정이 여러 개여서 번갈아 가며 배포해야 한다면 매번 브라우저 로그아웃과 로그인을 반복해야 합니다.</li>
<li>브라우저 창을 띄울 수 없는 원격 서버나 GitHub Actions 같은 자동화(CI/CD) 환경에서는 쓸 수 없습니다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="방법-2-api-토큰-환경변수-기반-비대화형-자동-배포-cicd-및-다계정-표준">[방법 2] API 토큰 환경변수 기반 비대화형 자동 배포 (CI/CD 및 다계정 표준)</h4>
<p>다계정 운영이나 배포를 무인 자동화할 때 사용하는 표준적인 방식입니다. 미리 발급받은 API 토큰을 환경변수로 넘겨주어 브라우저 팝업 없이 올립니다.<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-003.webp" alt="img-general-guide-cloudflare-pages-deployment-guide-ko-003"></p>
<pre><code class="language-bash"># 환경변수로 토큰과 계정 ID를 전달하며 배포 (Windows PowerShell 예시)
$env:CLOUDFLARE_API_TOKEN=&quot;발급받은_API_토큰&quot;
$env:CLOUDFLARE_ACCOUNT_ID=&quot;32자리_계정_ID&quot;
npx wrangler pages deploy .svelte-kit/cloudflare --project-name=my-blog-web --branch=production
</code></pre>
<ul>
<li><strong>어떻게 동작하나요?</strong>: Cloudflare 웹사이트에서 필요한 권한(Pages, D1, KV 등)을 가진 API 토큰을 미리 발급받아 환경변수나 설정 파일에 등록해 둡니다. 배포 명령을 실행할 때 이 토큰이 자동으로 주입되어 브라우저 창을 띄우지 않고 백그라운드에서 즉시 배포를 완료합니다.</li>
<li><strong>장점</strong>:<ul>
<li>인터넷 창이 번쩍거리며 열리지 않고 백그라운드에서 조용하고 빠르게 올라갑니다.</li>
<li>계정마다 서로 다른 토큰을 지정해 둘 수 있어서, <strong>여러 블로그나 사이트를 운영할 때 세션 충돌 없이 완벽하게 독립 배포</strong>할 수 있습니다.</li>
</ul>
</li>
<li><strong>알아둘 점</strong>: 최초 1회 Cloudflare 대시보드에서 API 토큰을 발급받는 과정이 필요합니다.</li>
</ul>
<hr>
<h2 id="3-글과-이미지는-어디에-저장될까요-d1과-kv의-쉬운-개념">💾 3. 글과 이미지는 어디에 저장될까요? (D1과 KV의 쉬운 개념)</h2>
<p>웹사이트 화면만 있다고 블로그가 돌아가는 것은 아닙니다. 작성한 글과 업로드한 사진을 안전하게 보관할 공간이 필요합니다. Cloudflare는 이를 위해 2가지 편리한 저장 공간을 제공합니다.<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-004.webp" alt="img-general-guide-cloudflare-pages-deployment-guide-ko-004"></p>
<ol>
<li><strong>Cloudflare D1 (글과 설정이 저장되는 관계형 데이터베이스)</strong>:<ul>
<li>엑셀 시트처럼 칸과 줄이 딱딱 맞춰진 표(Table) 형태의 관계형 데이터베이스(SQLite)입니다.</li>
<li>&quot;글 제목은 무엇인가?&quot;, &quot;어느 카테고리에 속하는가?&quot;, &quot;언제 작성되었는가?&quot;, &quot;사이트 이름은 무엇인가?&quot; 같은 정형화된 정보를 빠르고 안전하게 보관하고 검색합니다.</li>
</ul>
</li>
<li><strong>Workers KV (사진과 이미지를 저장하는 초고속 창고)</strong>:<ul>
<li>글에 첨부한 썸네일이나 이미지 파일처럼 용량이 크고 다양한 파일들을 전 세계 Cloudflare 창고에 넣어두고, 방문자가 요청할 때 1초도 안 걸려 즉시 꺼내 보여주는 역할을 합니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="4-스벨트킷-블로그-엔진의-실제-구성-엿보기">🧩 4. 스벨트킷 블로그 엔진의 실제 구성 엿보기</h2>
<p>스벨트킷 블로그 엔진(SvelteKit Blog Engine)은 위에서 살펴본 Cloudflare의 장점들을 살려 아래와 같이 설계되어 있습니다.<br><img src="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-005.webp" alt="img-general-guide-cloudflare-pages-deployment-guide-ko-005"></p>
<hr>
<h3 id="왜-방문자용-블로그와-관리자-페이지를-2개로-따로-쪼갰을까요">왜 방문자용 블로그와 관리자 페이지를 2개로 따로 쪼갰을까요?</h3>
<p>보통은 블로그와 관리자 화면을 하나의 프로그램으로 뭉쳐서 만듭니다. 하지만 스벨트킷 블로그 엔진은 이 둘을 <strong>완전히 독립된 2개의 Cloudflare Pages로 분리</strong>했습니다:</p>
<ol>
<li><strong>보안이 훨씬 안전해집니다</strong>: 관리자 로그인 창이나 글 작성/삭제 기능이 일반 방문자가 들어오는 블로그 주소에는 아예 들어있지 않습니다. 글을 쓰는 관리자 주소가 물리적으로 완전히 분리되어 있으므로 해킹 시도 자체를 원천 차단하기 좋습니다.</li>
<li><strong>방문자 화면이 엄청 가볍고 빨라집니다</strong>: 글을 편집하는 복잡한 에디터 도구들이 방문자 페이지에는 1바이트도 포함되지 않기 때문에, 핸드폰이나 느린 인터넷 환경에서도 블로그 글이 시원시원하게 순식간에 뜹니다.</li>
</ol>
<hr>
<h3 id="글을-쓰고-저장하는-과정">글을 쓰고 저장하는 과정</h3>
<ul>
<li>내가 <strong>관리자 화면</strong>에 들어가서 새 글을 쓰고 사진을 올리면, 그 내용은 Cloudflare의 **D1(데이터베이스)**과 **KV(사진 창고)**에 저장됩니다.</li>
<li>일반 독자가 <strong>블로그 웹사이트</strong>에 접속하면, 방금 저장된 D1과 KV에서 최신 글과 사진을 쏙 뽑아와서 예쁜 화면으로 보여줍니다.</li>
</ul>
<hr>
<h2 id="5-마치며">🚀 5. 마치며</h2>
<p>Cloudflare Pages는 예전처럼 복잡한 서버 명령어들을 외우거나 비싼 호스팅 비용을 낼 필요 없이, 누구나 가볍고 쾌적한 나만의 웹사이트를 운영할 수 있게 도와주는 훌륭한 기술입니다.</p>
<p>처음에는 **간편 브라우저 로그인(<code>wrangler login</code>)**으로 가볍게 블로그를 띄워보시고, 나중에 사이트 관리에 익숙해지거나 여러 개의 블로그를 운영하게 되면 <strong>API 토큰을 이용한 다계정 배포</strong>로 자연스럽게 확장해 보시는 것을 추천합니다.</p>
]]></content:encoded>
            <category>통합 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/cloudflare-pages-deployment-guide/desktop/img-general-guide-cloudflare-pages-deployment-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[[개발 일지] 2026-07-10]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/devlog-2026-07-10</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/devlog-2026-07-10</guid>
            <pubDate>Fri, 10 Jul 2026 23:07:44 GMT</pubDate>
            <description><![CDATA[v1.0.0.2 & v1.0.0.3 릴리즈 및 신규 테마 적용]]></description>
            <content:encoded><![CDATA[<h2 id="v1002-업데이트-내역">v1.0.0.2 업데이트 내역</h2>
<p>이번 릴리즈에서는 관리자 포스트 에디터의 편의성과 기능성을 높이기 위한 여러 개선 사항들이 반영되었습니다.</p>
<h3 id="주요-변경-사항">🚀 주요 변경 사항</h3>
<ol>
<li><p><strong>본문 이미지 링크 삽입 및 편집 기능 추가</strong></p>
<ul>
<li>에디터 내에 이미지를 삽입하거나 편집할 때, 이미지에 커스텀 링크 주소(URL) 및 새 창 열기 설정을 연동할 수 있도록 모달 UI를 확장했습니다.</li>
<li>Tiptap 에디터 로드 시 이미지 링크가 강제로 제거되는 문제를 방지하기 위해 HTML 전/후처리(Pre/Post-processing) 기능을 연동했습니다.</li>
</ul>
</li>
<li><p><strong>포스트 작성/수정 미리보기 이미지 캡션 시각화</strong></p>
<ul>
<li>포스트 미리보기 탭에서 이미지 아래의 의미론적 캡션(figcaption)이 정상적으로 렌더링되도록 기존 본문 처리 유틸리티를 재사용하여 동기화했습니다.</li>
</ul>
</li>
<li><p><strong>다국어 지원 실시간 글자수 카운터 탑재</strong></p>
<ul>
<li>글 작성 및 수정 화면 하단에 순수 글자 수를 실시간으로 집계해 주는 카운터 UI를 추가했습니다.</li>
<li>HTML 태그 및 마크다운 문법 기호 등을 자동으로 정화하여 실제 텍스트의 글자 수만 정밀하게 카운팅하며, 다국어 설정을 완벽하게 지원합니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="v1003-업데이트-내역">v1.0.0.3 업데이트 내역</h2>
<p>이번 릴리즈에서는 위젯 시스템의 구조적 결함을 전면 수정하고, 위젯 목록 UI 스타일을 통일했습니다.</p>
<h3 id="버그-수정">🐛 버그 수정</h3>
<ol>
<li><p><strong>위젯 신규 생성 직후 배치 시 렌더링 크래시 수정</strong></p>
<ul>
<li>새로 만든 위젯을 즉시 레이아웃에 배치할 때 <code>master-</code> 접두사가 누락된 채 저장되어 렌더링이 붕괴되던 치명적 크래시를 수정했습니다.</li>
</ul>
</li>
<li><p><strong>데스크톱/모바일 레이아웃 배치 시 <code>config</code> 데이터 유실 수정</strong></p>
<ul>
<li>위젯을 배치하는 과정에서 설정값(<code>config</code>)이 전혀 전달되지 않아 배치된 위젯이 아무런 데이터 없이 빈 상태로 저장되던 버그를 수정했습니다.</li>
</ul>
</li>
<li><p><strong>위젯 편집 모달 오픈 시 설정값 초기화 버그 수정</strong></p>
<ul>
<li>배치된 위젯의 편집 버튼을 눌렀을 때 <code>config</code>가 이미 객체임에도 <code>JSON.parse</code>를 이중으로 시도하여 SyntaxError가 발생하고, 모달의 모든 설정(limit, shadow 등)이 기본값으로 초기화되던 버그를 수정했습니다.</li>
</ul>
</li>
<li><p><strong><code>limit</code> 비정상 입력 유효성 검사 강화</strong></p>
<ul>
<li>위젯 생성 및 편집 시 <code>limit</code> 값이 비정상(<code>NaN</code>, 1 미만)으로 저장될 수 있던 취약점을 차단하고, 해당 경우 기본값 <code>5</code>로 정규화하도록 수정했습니다.</li>
</ul>
</li>
<li><p><strong>동일 타입 위젯 다중 배치 시 데이터 조회 범위 오류 수정</strong></p>
<ul>
<li>같은 타입의 위젯(예: 인기 포스트)이 여러 개 배치된 경우, 첫 번째 위젯의 <code>limit</code> 기준으로만 DB를 조회하여 나머지 위젯이 잘린 데이터를 받던 구조적 버그를 수정했습니다. 이제 동일 타입 위젯 중 가장 큰 <code>limit</code> 기준으로 조회한 뒤, 각 위젯이 자신의 설정에 맞게 독립적으로 데이터를 표시합니다.</li>
</ul>
</li>
</ol>
<h3 id="스타일">🎨 스타일</h3>
<ol>
<li><strong>위젯 목록 구분선 스타일 통일</strong><ul>
<li><code>RecentPostsWidget</code>과 <code>PopularPostsWidget</code>의 항목 간 구분 방식이 달라 시각적 일관성이 없던 문제를 수정했습니다. 두 위젯 모두 <code>border-bottom</code> 구분선과 <code>0.5rem</code> 패딩으로 통일했습니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="레이아웃-및-디자인-마이너-조정">🎨 레이아웃 및 디자인 마이너 조정</h2>
<p>블로그 레이아웃과 색상 등 전반적인 스타일 요소를 마이너 조정하여, 화면 구성이 깨지지 않고 자연스럽게 어우러지도록 다듬었습니다.</p>
<h3 id="주요-스타일-조정-사항">🛠️ 주요 스타일 조정 사항</h3>
<ol>
<li><p><strong>테마 컬러 및 톤 조정</strong></p>
<ul>
<li>주황색 계열의 서브 컬러를 포인트로 잡고, 전체적인 배경색과 카드 영역의 테두리 및 그림자 값을 다듬어 시각적인 부담을 줄였습니다.</li>
</ul>
</li>
<li><p><strong>데스크톱 2단 구성 레이아웃 배치</strong></p>
<ul>
<li>본문 영역과 사이드바(카테고리, 최근/인기 포스트 등)의 가로 너비 비율을 재조정하여 좌우 여백과 콘텐츠 정렬이 어긋나지 않도록 맞추었습니다.</li>
</ul>
</li>
<li><p><strong>헤더/푸터 및 모바일 메뉴 정렬</strong></p>
<ul>
<li>상단 헤더의 투명도 조정과 함께 모바일 메뉴 아이콘의 정렬 상태 및 하단 푸터 영역의 레이아웃이 깔끔하게 표시되도록 정리했습니다.</li>
</ul>
</li>
</ol>
]]></content:encoded>
            <category>개발 일지</category>
        </item>
        <item>
            <title><![CDATA[[개발 일지] 어쩌다가 철지난 블로그 엔진을 만들게 되었는가]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/devlog-why-i-built-a-blog-engine-in-2026</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/devlog-why-i-built-a-blog-engine-in-2026</guid>
            <pubDate>Thu, 09 Jul 2026 20:29:48 GMT</pubDate>
            <description><![CDATA[그냥 커피값이나 벌 생각으로 블로그 개설하려다가 블로그 엔진 만들게 된 이야기]]></description>
            <content:encoded><![CDATA[<h3 id="개인적인-생각을-정리하는-독백-형식의-글이라-평어로-작성된-점-양해-부탁드립니다"><strong>개인적인 생각을 정리하는 독백 형식의 글이라 평어로 작성된 점 양해 부탁드립니다</strong><br></h3><p>1.<br>상황이 이렇게 된 건 사실 유튜브의 탓이 크다(?).<br>뒤늦게 1인 개발자로 뭔가 해보려고 자료를 찾다보면 어마어마하게 많은 정보들이 쏟아져 나온다.<br>문제는 정보의 양은 넘쳐나는데 사실상 질은 엉망인 경우가 많다는거다.</p><p>2.<br>간단한 기초적인 내용이라면 정보가 많다고 해도 크게 문제될 건 없는데 조금만 전문적이거나 특수한 상황이 되면, 대체 이게 왜 검색에 걸려서 내가 이 웹에서 이걸 보고 있는지 도저히 이해가 안갈 정도로 완전 필요없는 내용의 정보를 접하게 되거나, 제목에는 내가 원하는 정보에 관한 내용이 들어가 있지만 정작 본문에는 그냥 문장 구조만 맞춘 의미없는 문자의 나열인 경우가 꽤 된다.<br>이게 유튜브 영상일 때는 진짜 화난다.</p><p>3.<br>그러다가 가끔씩 사막에서 오아시스 만나듯 알짜배기 정보를 줬던건 거의 대부분 개발자의 블로그 기록이었다.<br>정말 반갑고, 고맙고 그랬다.<br>그리고 성인 ADHD를 의심할만한 내 집중력은 '아 나도 개발자 블로그 하나 파서 누군가에게 도움이 되고, 내 커피값도 벌고 싶다(?)' 란 결정을 내리게 된다.</p><p>4.<br>그렇게 기존의 블로그 플랫폼들을 재점검 해봤지만 나에겐 뭔가 하나씩 치명적인 문제점들이 있었다.<br>우선 배포/유지하는데에 비용이 발생하는 것들은 모두 제외했다.<br>뭐 커피값이야 벌면 좋지만 사실상 그게 가능할 정도로 하려면 진짜 열심히 해야한다는 걸 아는데, 난 나를 잘 안다.<br>난 그렇게 열심히 못한다.<br>그렇기에 애초에 설치/유지 비용이 들어가면 절대 안된다는 결론을 시작부터 깔고 갔다.<br>그래서 처음 구상했던건 Proxmox로컬 서버에 워드프레스를 설치해서 쓰는거였다.<br>이렇게 쓰려면 최소한의 보안을 위해서 L2 스위치와 pfSense같은 방화벽 설치는 필수라고 봤다.<br>그렇게 어느 정도 구성을 하고나서 워드프레스를 다시 살펴보니, 커스텀이 너무 어려웠다.<br>이거 커스텀 하려면 PHP를 제대로 공부해야할것 같다는 판단이 들었을 때 이 방식은 포기하게 된다.</p><p>5.<br>그리고 빠르게 다른 방법들을 찾아봤지만 결국 뭔가 하나씩 걸렸다.<br>어떤 방법이건 설치를 하면 결국 그것에 대해 새로 공부를 해야한다는 점이 매우 피로감있게 다가왔다.<br>그 피로감이 '아 그냥 간단하게 내가 만들자' 란 생각으로 이끌었다.</p><p>6.<br>이때 이미 난 스벨트와 스벨트킷으로 이런저런 장난질을 많이 하고 있었던 터였다.<br>그리고 블로그를 만드는 건 매우 간단한 상황이었다.<br>문제는 내가 한 디자인을 내가 믿을수 없다는 거였다.<br>그래서 디자인을 바꾸기 쉽게 할 수 있는 구조로 만들자던게 결국 굴러굴러 여기까지 오게 됐다.</p><p>7.<br>실제로 디자인 에디터의 고도화 및 디버깅에 가장 많은 시간을 쏟았다.<br>최초에 '간단한 블로그 툴'을 만들자 하고 하루만에 코드의 90% 정도를 완성시켰다.<br>그리고 실제로 블로그를 만들어서 운영을 했다.<br>그러자 '이것도 필요하네?', '이 기능도 넣어야겠네', '이거 있으면 편하겠다', '이건 있어야지' 가 거의 무한으로 쏟아져나왔다.<br>그렇게 작년 11월말에 시작했던 프로젝트 아닌 프로젝트가 올해 3월에 기능 구현이 마무리가 되고 7월이 되어서야 <strong>일단</strong> 깃허브 오픈을 하게 됐다.</p><p>8.<br>정신 차려보니 기능이 너무 많아지고 코드도 복잡해져서 중간중간 모듈화하고 리팩토링을 진행했다.<br>시스템 자체를 갈아엎기도 여러번 했다.<br>최초에는 어드민은 Proxmox서버에 설치해서 로컬에서만 접속가능하게 하고 DB는 Supabase, 인증도 Supabase auth를 썼었다.<br>꽤나 심각한 콜드 스타트와 무료 플랜에서는 인증시에 프로젝트명이 노출되는 상황을 겪고 그냥 클라우드플레어 생태계를 쓰기로 하고 이전에 써봤던 Better-auth를 탑재하는 것으로 계획을 틀게 된다.<br>이후로도 내부적으로 로직이나 구조를 상당히 많이 바꾸게 된다.<br>게다가 윈도우에서 작업을 진행한 탓에 뭐 하나 고치면 실제 배포까지 해야 확인이 가능해서 진짜 어마어마한 삽질을 했다.</p><p>9.<br>거의 끝나갈 때 즈음엔 나 혼자서는 도저히 수정도 못할 정도로 코드가 너무 방대해져 있어서 직접 한것도 있지만 대부분 제미나이를 시켜서 진행했다.<br>거기다 문서화를 하지 않아서 하나하나 실제 기능과 비교해가며 문서화를 진행하다가 오류 발견하면 또 다시 수정작업에 들어갔다.<br>이렇게 3월부터 7월까지 문서화-&gt;오류 발견-&gt;수정의 무한 반복이었다.<br>그 와중에 '이건 있어야지'랑 '이 기능 있었는데 어디갔지?'가 막 튀어나와서 진짜 미치는 줄 알았다.</p><p>10.<br>엎친데 덮친 격으로 제미나이가 진짜 맛이 가버리는 사태가 매우 종종 발생했다.<br>코드 한 줄 고치면 되는 걸 상관도 없는 모듈을 뜯어고치질 않나 뭐 하여간 제미나이가 깽판만 안쳤어도 두 달은 일찍 끝났을거라 확신한다.<br>여튼 깃허브 오픈 직전까지 극심한 스트레스에 시달리다 그냥 '이건 있어야지'를 다 쳐내고 되어있는 거만 정리해서 올리기로 결정했다.<br>그게 v1.0.0.0이었다.</p><p>11.<br>그 후로 바로 v1.0.0.1이 나온다.<br>'있는 거'만 정리해서 올렸어야 하는데 정리가 제대로 안된 것들을 바로 알아챈거다.<br>아마 이런게 더 있을거라 확신이 들어서 매우 불안하기도 하다.<br>이미 올릴 때부터 직감해서 v1.0.0.x까지 해뒀던듯도 싶다.</p><p>12.<br>뭐 이렇게 해서 스벨트킷 블로그 엔진이 만들어졌습니다.<br>글마무리가 애매하니 PageSpeed Insights 점수나 올리고 마무리하겠습니다.<br>애드센스는 신청 후 대기 중이지만 일단 코드가 삽입되어 있고 CDN캐시도 2분 간격으로 설정되어 있어서 파되었다 재생성되는 사이에 테스트가 진행되면 성능에 영향을 주는건지 정확한 이유는 모르겠으나 현재 점수의 변동이 꽤나 큽니다.<br>평균 정도 나왔을 때 점수니 그냥 참고로만 봐주세요.<br>애드센스 승인되면 다시 PageSpeed Insights 보고서 올리겠습니다.<br>좋은 하루 되세요. :)</p><figure data-align="center"><img src="https://sveltekitblog.com/images/posts/devlog-why-i-built-a-blog-engine-in-2026/desktop/img-devlog-devlog-why-i-built-a-blog-engine-in-2026-ko-001.webp" alt="img-devlog-devlog-why-i-built-a-blog-engine-in-2026-ko-001" data-align="center" data-caption="모바일 점수. 애드센스(신청대기중) GA4, 구글 서치콘솔, 네이버 서치어드바이저 탑재 상태"><figcaption>모바일 점수. 애드센스(신청대기중) GA4, 구글 서치콘솔, 네이버 서치어드바이저 탑재 상태</figcaption></figure><figure data-align="center"><img src="https://sveltekitblog.com/images/posts/devlog-why-i-built-a-blog-engine-in-2026/desktop/img-devlog-devlog-why-i-built-a-blog-engine-in-2026-ko-002.webp" alt="img-devlog-devlog-why-i-built-a-blog-engine-in-2026-ko-002" data-align="center" data-caption="데스크탑 점수. 애드센스(신청 대기중), GA4, 구글 서치콘솔, 네이버 서치어드바이저"><figcaption>데스크탑 점수. 애드센스(신청 대기중), GA4, 구글 서치콘솔, 네이버 서치어드바이저</figcaption></figure><p></p>]]></content:encoded>
            <category>개발 일지</category>
            <enclosure url="https://sveltekitblog.com/images/posts/devlog-why-i-built-a-blog-engine-in-2026/desktop/img-devlog-devlog-why-i-built-a-blog-engine-in-2026-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[[개발 일지] 2026-07-08]]></title>
            <link>https://fixedblog-3lz.pages.dev/devlog/devlog-2026-07-08</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/devlog/devlog-2026-07-08</guid>
            <pubDate>Wed, 08 Jul 2026 14:00:14 GMT</pubDate>
            <description><![CDATA[[개발 일지] 2026년 07월 08일 - 업데이트 상황]]></description>
            <content:encoded><![CDATA[<a href="https://github.com/sveltekitblog/sveltekitblogengine" target="_blank" rel="noopener noreferrer"><figure data-align="center"><img src="https://sveltekitblog.com/images/posts/devlog-2026-07-08/desktop/img-devlog-devlog-2026-07-08-ko-001.webp" alt="img-devlog-devlog-2026-07-08-ko-001" data-align="center" data-caption="Sveltekit Blog Engine v1.0.0.1 릴리즈" data-link-url="https://github.com/sveltekitblog/sveltekitblogengine" data-link-target="_blank"><figcaption>Sveltekit Blog Engine v1.0.0.1 릴리즈</figcaption></figure></a><p>1.<br>Sveltekit Blog Engin v1.0.0.1 릴리즈 업데이트.<br>어드민 - 피드백 - 휴지통에 페이지네이션 및 일괄 삭제/복구 기능 추가.<br>다국어 설정(hreflang) 태그 바인딩 설정 추가.</p><figure data-align="center"><img src="https://sveltekitblog.com/images/posts/devlog-2026-07-08/desktop/img-devlog-devlog-2026-07-08-ko-002.webp" alt="img-devlog-devlog-2026-07-08-ko-002" data-align="center" data-caption="소셜 로그인 추가된 화면"><figcaption>소셜 로그인 추가된 화면</figcaption></figure><p></p><p><br>2.<br>소셜 로그인 추가 - 기존의 이메일 로그인에 Github, Google, Naver 추가.</p><p>3.<br>Sveltekit Blog Engin v1.0.0.0 릴리즈 때도 아무 언질이 없다가 v1.0.0.1을 올린 후에야 개발 일지라는 걸 써야겠다는 판단이 들어서 뒤늦게라도 올림.<br>우선 상세 메뉴얼부터 최대한 포스팅 후 개발 관련 이야기들을 포스팅할 예정.<br>상세 메뉴얼은 마크다운으로, 개발 일지는 텍스트 에디터로 작성함.</p><p>4.<br>이 포스팅 쓰면서 발견한 문제점.<br>첫째 - 이미지에 링크 거는 기능이 없다. <s>이 포스팅은 수동으로 추가함.</s> 기능 추가함.<br>둘째 - <s>까먹음(;;)</s> 본문 텍스트가 몇 글자인지 표기해주는 기능을 거의 프로젝트 시작할 때 완성했었는데 완전 갈아엎어지는 상황에 어디론가 떠내려간듯.</p><p>5.<br>기능 구현이 온전히 끝나야 상세 메뉴얼도 각잡고 쓸텐데 이거 뭐 메뉴얼 쓰다가 에러 발견, 포스팅 하다가 기능 누락 발견...<br>끝이 안 보임.</p>]]></content:encoded>
            <category>개발 일지</category>
            <enclosure url="https://sveltekitblog.com/images/posts/devlog-2026-07-08/desktop/img-devlog-devlog-2026-07-08-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[웹 폰트 설정법 및 로딩 성능 최적화 매뉴얼]]></title>
            <link>https://fixedblog-3lz.pages.dev/detail-manual/admin-font-settings</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/detail-manual/admin-font-settings</guid>
            <pubDate>Tue, 07 Jul 2026 06:36:18 GMT</pubDate>
            <description><![CDATA[SvelteKit 블로그 어드민에서 웹 폰트를 안전하게 구성하고, Cloudflare Pages 환경에서 로딩 속도를 향상시키기 위해 구현된 폰트 프록싱 및 캐싱 최적화 기술을 완벽히 마스터합니다.]]></description>
            <content:encoded><![CDATA[<p><img src="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" alt="img-detail-manual-setup-guide-ko-001"></p>
<h1 id="블로그-어드민-웹-폰트-설정-및-로딩-최적화-상세-설명서">✍️ 블로그 어드민 웹 폰트 설정 및 로딩 최적화 상세 설명서</h1>
<p>본 프로젝트는 블로그의 시각적 완성도와 가독성을 높이기 위해 어드민 단에서 손쉽게 폰트를 커스터마이징할 수 있는 직관적인 편집 도구를 제공합니다. </p>
<p>단순히 폰트 이름을 적용하는 데 그치지 않고, 모바일 환경이나 네트워크 속도가 제한된 환경에서도 최고의 로딩 속도와 성능 점수(Lighthouse Performance)를 유지할 수 있도록 설계된 특별한 성능 최적화 엔진이 함께 동작합니다.</p>
<p>본 설명서는 어드민 디자인 에디터에서의 구체적인 폰트 설정 방법과, 내부적으로 폰트가 어떻게 최적화되어 방문자에게 빠르게 서빙되는지 동작 개념을 상세히 다룹니다.</p>
<hr>
<h2 id="1-디자인-에디터-내-폰트-설정-세부-항목">📂 1. 디자인 에디터 내 폰트 설정 세부 항목</h2>
<p>어드민 디자인 에디터 페이지의 설정 메뉴에서 블로그 레이아웃의 성격에 맞춰 글꼴 종류, 크기, 두께를 세밀하게 편집할 수 있습니다.</p>
<h3 id="1-1-글로벌-웹-폰트-설정-전체-페이지-영향">✍️ 1-1. 글로벌 웹 폰트 설정 (전체 페이지 영향)</h3>
<p>블로그의 전체적인 본문 영역과 기본 인터페이스 전반에 기본값으로 바인딩되는 글로벌 폰트를 관리합니다.</p>
<ul>
<li><strong>기본 글꼴 설정 (Font Family)</strong>:<ul>
<li>블로그의 가장 베이스가 되는 본문 서체를 지정합니다.</li>
<li><em>입력 방법</em>: 브라우저가 인식할 수 있는 글꼴 이름을 따옴표와 함께 적어줍니다. 비워두면 시스템 기본 폰트인 <code>Inter</code> 서체가 전체 블로그에 기본적으로 상속됩니다. (예: <code>&#39;Noto Serif KR&#39;, serif</code> 또는 <code>Pretendard</code>)</li>
</ul>
</li>
<li><strong>추가할 구글 웹 폰트 (Google Font Name)</strong>:<ul>
<li>로고 텍스트나 핵심 위젯 타이틀 등 특정 영역에 포인트로 입히고 싶은 구글 폰트의 영문 명칭을 등록합니다.</li>
<li><em>입력 방법</em>: 쉼표(<code>,</code>)나 세미콜론(<code>;</code>)을 사용해 여러 개의 폰트를 나열하여 등록할 수 있습니다. (예: <code>Noto Serif KR, Newsreader</code>)</li>
<li><em>연동 효과</em>: 이곳에 입력한 커스텀 폰트명들은 헤더의 로고 폰트 설정이나 각 영역의 개별 글꼴 선택 옵션 드롭다운 메뉴에 자동으로 생성되어 편리하게 매핑할 수 있게 됩니다.</li>
</ul>
</li>
<li><strong>기본 글자 크기 (Base Size) 및 모바일 기본 글자 크기 (Base Size - Mobile)</strong>:<ul>
<li>데스크톱과 모바일 환경 각각에서 서비스 전체 레이아웃의 기준이 되는 본문 글자 크기를 정의합니다.</li>
<li><em>입력 방법 및 팁</em>: <code>16px</code> 이나 <code>14px</code> 같은 정밀한 <strong>절대 단위(px)</strong> 뿐만 아니라, 상위 환경이나 사용자의 브라우저 기본 글 크기 설정에 맞추어 유연하게 변동하는 <strong>상대 단위(rem, em)</strong> 형식의 주입도 완벽히 호환됩니다. (예: 데스크톱 <code>1rem</code> 또는 <code>16px</code> / 모바일 <code>0.875rem</code> 또는 <code>14px</code>)</li>
</ul>
</li>
</ul>
<h3 id="1-2-헤더-및-로고-폰트-설정">🎨 1-2. 헤더 및 로고 폰트 설정</h3>
<p>상단 네비게이션 헤더에 노출되는 브랜딩 로고 텍스트에 독립적인 디자인을 부여합니다.</p>
<ul>
<li><strong>폰트 패밀리 (Font Family)</strong>:<ul>
<li>로고 텍스트에 입힐 서체를 드롭다운에서 선택합니다.</li>
<li>기본 설정인 <code>기본 폰트 상속 (Inherit)</code>을 고르면 전체 베이스 폰트를 따라가며, 글로벌 웹 폰트 설정의 <strong>[추가할 구글 웹 폰트]</strong> 영역에 지정해 두었던 폰트가 있을 경우 목록에 자동으로 표시되어 개별 적용이 가능합니다.</li>
</ul>
</li>
<li><strong>폰트 크기 및 모바일 폰트 크기</strong>:<ul>
<li>해상도 환경 변화에 따라 로고 글자가 찌그러지거나 레이아웃을 해치지 않도록 데스크톱 크기와 모바일 크기를 각각 다른 가독성 배율로 할당할 수 있습니다. (예: 데스크톱 <code>1.5rem</code> / 모바일 <code>1.2rem</code>)</li>
</ul>
</li>
<li><strong>폰트 굵기 (Font Weight)</strong>:<ul>
<li>로고의 시각적 존재감을 조절하기 위해 <code>100</code>부터 <code>900</code>까지 100 단위의 정밀한 두께 선택 옵션을 제공합니다.</li>
<li><em>주의 사항</em>: 적용된 폰트 종류에 따라 지원하는 굵기 단계가 서로 다릅니다. 선택한 웹 폰트가 특정 두께(예: 100 또는 900)를 제공하지 않을 경우 브라우저가 제공 가능한 가장 유사한 굵기로 대체 렌더링하므로, 사용하려는 서체의 굵기 제공 스펙을 미리 확인하는 것이 좋습니다.</li>
</ul>
</li>
</ul>
<h3 id="1-3-글로벌-위젯-스타일-설정">🧩 1-3. 글로벌 위젯 스타일 설정</h3>
<p>사이드바나 하단 영역 등에 배치되는 개별 위젯의 타이틀 및 구성 항목의 텍스트 스타일을 조정합니다.</p>
<ul>
<li><strong>글로벌 위젯 제목 스타일</strong>: 모든 위젯 상단에 노출되는 제목(타이틀) 영역의 폰트 종류, 글자 크기, 두께, 색상을 설정합니다.</li>
<li><strong>글로벌 위젯 항목 스타일</strong>: 위젯 카드를 구성하는 개별 내용물 및 링크 텍스트에 적용할 스타일을 설정합니다.</li>
<li><em>상속 규칙</em>: 글로벌 위젯 설정 역시 글꼴 이름을 공란으로 비워두게 되면 최상단 글로벌 폰트를 상속받아 깨짐 없는 통일성을 유지하게 됩니다.</li>
</ul>
<hr>
<h2 id="2-폰트-로딩-성능-최적화와-작동-메커니즘">⚡ 2. 폰트 로딩 성능 최적화와 작동 메커니즘</h2>
<p>블로그가 사용자 화면에 렌더링될 때 흔히 발생하는 폰트 로딩 지연 현상은 블로그 성능 점수를 크게 떨어뜨리는 주범입니다. 이를 예방하기 위해 본 엔진은 서버리스 환경에 특화된 고유의 폰트 가속 아키텍처를 내장하고 있습니다.</p>
<h3 id="2-1-시스템-내장-기본-글꼴-필터링">🛡️ 2-1. 시스템 내장 기본 글꼴 필터링</h3>
<p>방문자의 PC나 스마트폰 운영체제 내에 이미 기본 탑재되어 제공되는 글꼴(예: <code>Pretendard</code>, <code>Arial</code>, <code>system-ui</code> 등)의 경우, 블로그 서버가 구글 폰트 서버에 중복 요청을 보내거나 다운로드하지 않도록 서버 단에서 지능적으로 걸러내어 불필요한 네트워크 트래픽을 원천 차단합니다.</p>
<h3 id="2-2-외부-요청-차단-및-자체-프록시-중계">🔄 2-2. 외부 요청 차단 및 자체 프록시 중계</h3>
<p>일반적인 웹사이트처럼 외부 Google Fonts 주소(<code>fonts.googleapis.com</code> 및 <code>gstatic.com</code>)에 클라이언트 브라우저가 직접 접속하여 폰트를 가져오면 DNS 쿼리와 보안 인증을 새로 거쳐야 하므로 수백 밀리초의 렌더링 지연이 발생합니다.</p>
<ul>
<li><strong>중계 스타일시트 생성</strong>: 블로그 렌더링 시 외부 구글 서버 주소를 호출하는 대신 자체 엔진 API를 통해 폰트 스타일시트(CSS)를 우회하여 확보합니다.</li>
<li><strong>내부 링크 치환</strong>: 스타일시트 내부에서 실제 폰트 파일(.woff2)을 다운로드하는 경로를 분석한 뒤, 구글 서버의 외부 주소를 블로그 내부 도메인 프록시 주소로 <strong>일괄 교체</strong>합니다.</li>
<li><strong>보안 필터 탑재</strong>: 중계 API는 지정된 구글 공식 폰트 저장소 도메인 이외의 무단 외부 주소 접근을 엄격히 통제하여 임의의 유해 코드 릴레이가 일어날 수 없도록 안전하게 통제합니다.</li>
</ul>
<h3 id="2-3-cloudflare-pages-cdn-및-브라우저-로컬-캐싱">💾 2-3. Cloudflare Pages CDN 및 브라우저 로컬 캐싱</h3>
<p>자체 프록시를 통해 요청된 모든 폰트 명세 파일과 폰트 데이터(.woff2)는 Cloudflare의 글로벌 분산 에지 서버(CDN)에 1일 동안 영구 캐싱됩니다.</p>
<ul>
<li>또한 브라우저에게도 강력한 로컬 캐싱 명령 헤더를 실어 응답하므로, 사용자가 블로그 내의 다른 글을 클릭하거나 다음번에 다시 방문할 때는 인터넷 연결 속도와 관계없이 본인 기기 메모리에서 글꼴을 0ms 만에 즉시 렌더링하게 됩니다. </li>
<li>이를 통해 페이지 레이아웃이 폰트 로딩 속도 차이로 인해 덜컥거리며 깜빡이는 현상(CLS)을 완벽에 가깝게 차단해 줍니다.</li>
</ul>
<h3 id="2-4-pretendard-서체-특화-가속-적용">🚀 2-4. Pretendard 서체 특화 가속 적용</h3>
<p>한국어 가독성의 표준으로 자리 잡은 오픈소스 서체인 <code>Pretendard</code>를 기본 폰트로 선언할 경우, 블로그 렌더링 속도를 비약적으로 단축하기 위한 <strong>우회 가속 통로</strong>가 자동으로 개설됩니다.</p>
<ul>
<li><strong>병렬 다운로드 시너지</strong>: 구글 서버 및 중계 API 프록시망을 거치지 않고, 전 세계에 엣지가 분산된 초고속 오픈소스 CDN(jsDelivr) 정적 망을 통해 다른 스크립트와 함께 패러렐(Parallel)하게 폰트 파일을 끌어옵니다. </li>
<li><strong>렌더링 최적화</strong>: 한글 서체의 특성상 복잡하고 용량이 큰 바이너리를 가장 신속하게 확보하여, 첫 화면 진입 시 글자가 늦게 나타나거나 폰트가 나중에 바뀌는 현상을 근본적으로 예방합니다.</li>
</ul>
<hr>
<h2 id="3-실제-적용-시-주의-사항-및-추천-설정값">💡 3. 실제 적용 시 주의 사항 및 추천 설정값</h2>
<blockquote>
<p>[!IMPORTANT]<br><strong>💡 어드민 폰트 수정 시 꼭 기억해야 할 세 가지 행동 수칙</strong></p>
<ol>
<li><strong>구글 웹 폰트 이름의 정확한 철자 준수</strong>:<ul>
<li>추가할 구글 웹 폰트 항목에 입력하는 영어 이름은 구글 폰트 라이브러리에 등록된 공식 영문 명칭과 100% 동일해야 합니다. 철자가 틀리거나 띄어쓰기가 비정상적일 경우, 로딩 엔진이 폰트를 찾지 못해 시스템 기본 대체 글꼴(Sans-serif)로 화면이 강제 전환될 수 있습니다.</li>
</ul>
</li>
<li><strong>데스크톱과 모바일의 글자 크기 균형 맞추기</strong>:<ul>
<li>모바일 뷰에서도 데스크톱 기본 글자 크기를 그대로 가져가면 화면 가독성이 나빠집니다. 모바일 크기 항목에 반드시 <code>2px</code> 정도 더 작은 값을 개별 바인딩하여 반응형 인터페이스를 부드럽게 유지하십시오.</li>
</ul>
</li>
<li><strong>안정적인 한국어 기본 대체 폰트(Fallback) 선언</strong>:<ul>
<li>글로벌 폰트 필드에 커스텀 폰트를 적용할 때 <code>&#39;Noto Serif KR&#39;, serif</code>처럼 가장 뒤에 시스템 범용 대체 이름(<code>serif</code> 또는 <code>sans-serif</code>)을 함께 나열해주면 폰트 로드가 일시적으로 지연되는 찰나의 순간에도 글꼴이 깨지지 않고 안정적으로 화면을 렌더링할 수 있습니다.</li>
</ul>
</li>
</ol>
</blockquote>
]]></content:encoded>
            <category>상세 메뉴얼</category>
            <enclosure url="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[CLI 환경에서 Cloudflare Wrangler 로그인 및 API 토큰 설정 상세 설명서]]></title>
            <link>https://fixedblog-3lz.pages.dev/detail-manual/wrangler-login-guide</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/detail-manual/wrangler-login-guide</guid>
            <pubDate>Mon, 06 Jul 2026 10:49:33 GMT</pubDate>
            <description><![CDATA[브라우저 연동이 불가능한 SSH 원격 서버 및 CI/CD 환경에서 API 토큰을 발급하여 Wrangler의 비대화형 인증 세션을 구축하는 방법을 완벽히 마스터합니다.]]></description>
            <content:encoded><![CDATA[<p><img src="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" alt="img-detail-manual-setup-guide-ko-001"></p>
<h1 id="cli-환경에서-cloudflare-wrangler-로그인-및-api-토큰-설정-상세-설명서">🔑 CLI 환경에서 Cloudflare Wrangler 로그인 및 API 토큰 설정 상세 설명서</h1>
<p>본 프로젝트는 단일 명령어로 Cloudflare의 데이터베이스, 저장소, 빌드 파이프라인을 구축해주는 자동화 셋업 스크립트를 제공합니다. </p>
<p>이 스크립트는 내부적으로 Cloudflare의 명령행 도구인 <code>wrangler</code> CLI를 사용합니다. 일반적인 로컬 개발 PC 환경에서는 웹 브라우저를 통한 대화형 로그인(<code>npx wrangler login</code>)이 즉시 실행되지만, **브라우저를 열 수 없는 완전한 CLI 환경(예: 원격 SSH Linux 서버, headless 가상 머신, CI/CD 배포 파이프라인)**에서는 로그인 세션을 대화형으로 수립할 수 없는 제약이 발생합니다.</p>
<p>본 매뉴얼은 이러한 환경 제약을 극복하기 위해 <strong>Cloudflare API 토큰</strong>을 활용하여 Wrangler의 비대화형 인증 세션을 안전하게 구축하는 방법을 상세히 안내합니다.</p>
<hr>
<h2 id="1-도메인-설정-전후에-따른-필수-최소-권한-구분">📂 1. 도메인 설정 전후에 따른 필수 최소 권한 구분</h2>
<p>API 토큰을 발급받기 전에, 현재 본인의 인프라 배포 단계를 파악하는 것이 중요합니다. 불필요한 보안 위협을 낮추기 위해 도메인 연결 상태에 따라 필요한 권한을 분리하여 적용합니다.</p>
<h3 id="1-1-초기-배포-단계-자체-도메인-연동-전">🔌 1-1. 초기 배포 단계 (자체 도메인 연동 전)</h3>
<p>프로젝트를 처음 배포하여 Cloudflare Pages가 제공하는 기본 도메인인 <code>*.pages.dev</code> 주소만 사용할 경우, 브라우저를 통해 연동하는 일반 로그인 상태와 동일하게 <strong>Zone(도메인) 수준의 어떠한 권한도 필요치 않습니다.</strong> 오직 <strong>계정(Account) 수준의 핵심 리소스 권한</strong>만으로 셋업 스크립트 실행과 D1 마이그레이션이 문제없이 가동됩니다.</p>
<ul>
<li><strong>필수 최소 권한 목록</strong>:<ul>
<li><code>D1</code> — <strong>Edit</strong> (D1 DB 생성, 스키마 마이그레이션 실행 및 쿼리 작동용)</li>
<li><code>Workers KV Storage</code> — <strong>Edit</strong> (블로그 미디어 업로드용 KV 네임스페이스 제어용)</li>
<li><code>Cloudflare Pages</code> — <strong>Edit</strong> (모노레포의 블로그 및 어드민 프로젝트 배포용)</li>
<li><code>Workers Scripts</code> — <strong>Edit</strong> (Workers 스크립트 및 CLI 설정 제어용)</li>
</ul>
</li>
</ul>
<h3 id="1-2-자체-도메인-연동-및-cdn-에지-캐시-사용-단계-추후-설정">🌐 1-2. 자체 도메인 연동 및 CDN 에지 캐시 사용 단계 (추후 설정)</h3>
<p>블로그에 개인 도메인을 연결하고, 성능 고도화를 위해 에지 CDN 캐시를 강제 비우는(Purge) 기능 등을 활성화할 때 비로소 <strong>Zone 수준 권한</strong>이 요구됩니다.</p>
<ul>
<li><strong>추가 필수 권한</strong>:<ul>
<li><code>Zone: Cache Purge</code> — <strong>Edit</strong> (배포 완료 시점이나 콘텐츠 갱신 시 CDN 에지 서버의 캐시를 즉시 제거하기 위해 필요)</li>
</ul>
</li>
<li><strong>보안 모범 사례</strong>: 토큰의 스코프를 제한할 때, <strong>Zone Resources</strong>를 &#39;All Zones&#39;로 열지 말고 사용 중인 특정 고유 도메인(Zone)으로 스코프를 한정하여 토큰을 발급받는 것이 안전합니다.</li>
<li>*참고: 자체 도메인 세팅 방법 및 캐시 제어 메커니즘의 세부 정보는 추후 별도로 발행될 **&#39;자체 도메인 설정에 관한 상세 설명서&#39;*<em>에서 깊이 있게 다룰 예정이므로, 초기 구축 단계인 지금은 해당 설정을 생략하고 진행하셔도 무방합니다.</em></li>
</ul>
<hr>
<h2 id="2-cloudflare-api-토큰-생성-절차">🛠️ 2. Cloudflare API 토큰 생성 절차</h2>
<p>비대화형 로그인을 가능케 하는 토큰은 Cloudflare 대시보드에서 다음과 같은 순서로 간단히 생성할 수 있습니다.</p>
<ol>
<li><strong>Cloudflare 대시보드 로그인</strong>:<ul>
<li><a href="https://dash.cloudflare.com/">Cloudflare Dashboard</a>에 접속하여 로그인합니다.</li>
</ul>
</li>
<li><strong>토큰 생성 메뉴 진입</strong>:<ul>
<li>우측 상단 프로필 아이콘을 클릭하고 **[My Profile] (프로필)**을 선택합니다.</li>
<li>좌측 메뉴에서 **[API Tokens] (API 토큰)**을 클릭하고, 우측의 <strong>[Create Token] (토큰 생성)</strong> 버튼을 클릭합니다.</li>
</ul>
</li>
<li><strong>사용자 지정 토큰 설정</strong>:<ul>
<li>여러 템플릿 중 최하단의 <strong>[Create Custom Token] (사용자 지정 토큰 생성)</strong> 영역의 **[Get Started] (시작하기)**를 선택합니다.</li>
</ul>
</li>
<li><strong>권한(Permissions) 할당</strong>:<ul>
<li><strong>Token Name</strong>: 구분이 쉬운 이름 입력 (예: <code>sveltekit-blog-cli-token</code>)</li>
<li><strong>Permissions</strong>:<ul>
<li><code>계정</code> (Account) -&gt; <code>D1</code> -&gt; <code>편집</code> (Edit)</li>
<li><code>계정</code> (Account) -&gt; <code>Workers KV 저장소</code> (Workers KV Storage) -&gt; <code>편집</code> (Edit)</li>
<li><code>계정</code> (Account) -&gt; <code>Cloudflare Pages</code> -&gt; <code>편집</code> (Edit)</li>
<li><code>계정</code> (Account) -&gt; <code>Workers 스크립트</code> (Workers Scripts) -&gt; <code>편집</code> (Edit)</li>
</ul>
</li>
</ul>
</li>
<li><strong>스코프 제한</strong>:<ul>
<li><strong>계정 리소스</strong> (Account Resources): <code>포함</code> (Include) -&gt; 본인의 <code>Cloudflare 계정 선택</code> (권한 범위를 내 계정 내부로만 좁혀 보안 위협을 격리합니다.)</li>
</ul>
</li>
<li><strong>생성 완료 및 보관</strong>:<ul>
<li>**[Continue to summary] (요약 계속)**를 누른 뒤 **[Create Token] (토큰 생성)**을 클릭하여 생성된 고유 토큰 값(알파벳과 기호로 구성된 긴 키 문자열)을 안전한 곳에 복사해 둡니다. (이 값은 재확인이 불가하므로 유실 시 다시 발급받아야 합니다.)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="3-발급된-api-토큰-적용-및-환경변수-주입법">💻 3. 발급된 API 토큰 적용 및 환경변수 주입법</h2>
<p>인터랙티브 브라우저 로그인 창을 띄울 수 없으므로, 시스템 환경변수에 발급받은 토큰 값을 주입하여 Wrangler가 자동으로 이를 조회하고 인증을 수립하게 유도해야 합니다.</p>
<h3 id="3-1-os-환경별-터미널-환경변수-주입-커맨드">3-1. OS 환경별 터미널 환경변수 주입 커맨드</h3>
<p>셋업 스크립트를 구동할 터미널 콘솔 창에 OS 유형별로 아래의 명령어를 입력합니다.</p>
<ul>
<li><strong>Linux / macOS / Git Bash (Bash Shell)</strong>:<pre><code class="language-bash">export CLOUDFLARE_API_TOKEN=&quot;발급받은_토큰_값&quot;
</code></pre>
</li>
<li><strong>Windows Command Prompt (CMD)</strong>:<pre><code class="language-cmd">set CLOUDFLARE_API_TOKEN=발급받은_토큰_값
</code></pre>
</li>
<li><strong>Windows PowerShell</strong>:<pre><code class="language-powershell">$env:CLOUDFLARE_API_TOKEN=&quot;발급받은_토큰_값&quot;
</code></pre>
</li>
</ul>
<blockquote>
<p>[!NOTE]<br><strong>💻 macOS 환경에서의 실행 및 테스트 안내</strong></p>
<ul>
<li>본 가이드는 <strong>Windows 10/11 및 Linux(Ubuntu 22.04 LTS)</strong> 물리적 콘솔 환경에서 직접 명령행 구동 및 토큰 연동 유효성을 사전에 전수 검증하였습니다.</li>
<li>다만 작성자 개인의 개발 장비 부재로 인해 macOS(맥북 환경)에서의 실기기 테스트는 거치지 못했습니다. 크로스 플랫폼 CLI 동작 원리상 macOS에서도 Bash/Zsh 터미널을 통해 동일하게 <code>export</code> 구문으로 완벽히 정상 가동될 것으로 판단됩니다.</li>
</ul>
</blockquote>
<h3 id="3-2-로컬-개발-환경-파일-연동-및-cicd-연동">3-2. 로컬 개발 환경 파일 연동 및 CI/CD 연동</h3>
<ul>
<li><strong>로컬 개발 (.dev.vars)</strong>:<br>매번 터미널 창을 열 때마다 환경변수를 치기 번거롭다면, <code>apps/blog/.dev.vars</code> 및 <code>apps/admin/.dev.vars</code> 파일에 아래와 같이 등록해 두면 로컬 <code>wrangler dev</code> 구동 시 해당 파일에서 토큰을 자동으로 파싱하여 바인딩 처리합니다.<pre><code class="language-env">CLOUDFLARE_API_TOKEN=&quot;발급받은_토큰_값&quot;
</code></pre>
</li>
<li><strong>CI/CD 파이프라인 (GitHub Actions)</strong>:<br>깃허브 레포지토리의 <code>Settings</code> -&gt; <code>Secrets and variables</code> -&gt; <code>Actions</code> -&gt; <code>Repository secrets</code> 메뉴로 가셔서 <code>CLOUDFLARE_API_TOKEN</code> 이라는 이름으로 발급받은 토큰을 추가한 뒤, yaml 워크플로우 파일에서 아래와 같이 활용할 수 있습니다.<pre><code class="language-yaml">- name: Build and Deploy SvelteKit Apps
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
  run: npm run deploy:blog
</code></pre>
</li>
</ul>
<hr>
<h2 id="4-토큰-인증-상태-검증-및-디버깅">🔍 4. 토큰 인증 상태 검증 및 디버깅</h2>
<p>토큰 환경변수가 시스템 세션 상에 안전하게 주입되어 정상적으로 바인딩되었는지 확인하기 위한 디버깅 커맨드입니다.</p>
<h3 id="4-1-wrangler-whoami">4-1. wrangler whoami</h3>
<p>터미널에서 아래 명령을 내려 토큰이 유효한 계정을 매칭하고 있는지 검증합니다.</p>
<pre><code class="language-bash">npx wrangler whoami
</code></pre>
<ul>
<li><strong>검증 통과 시</strong>: 현재 토큰이 소속된 계정 정보(Account Name, Account ID) 및 D1, KV 등의 제어 권한 바인딩 목록이 콘솔에 깔끔하게 성공 출력됩니다.</li>
<li><strong>로그인 세션 정리 (wrangler logout)</strong>:<br>만약 이전에 로컬 웹 브라우저 로그인 방식으로 맺어놓은 세션 찌꺼기가 남아 토큰과 간섭을 일으키는 것 같다면, 먼저 로그아웃 명령어를 수행하여 로컬 컴퓨터 내의 기존 인증 세션을 완전히 초기화한 후 환경변수를 다시 적용하는 것을 권장합니다.<pre><code class="language-bash">npx wrangler logout
</code></pre>
</li>
</ul>
<hr>
<h2 id="5-자주-마주치는-문제-및-해결-방안-troubleshooting">⚠️ 5. 자주 마주치는 문제 및 해결 방안 (Troubleshooting)</h2>
<h3 id="1-authentication-error-code-10000">🚨 1. Authentication Error (Code: 10000)</h3>
<ul>
<li><strong>원인</strong>: 셋업 스크립트 실행 중 Cloudflare 인증 실패 오류가 표시되는 상황입니다.</li>
<li><strong>해결</strong>: 복사하여 주입한 <code>CLOUDFLARE_API_TOKEN</code> 값 내에 오탈자나 공백이 포함되었는지 확인하세요. 또한, 대시보드에서 토큰을 발급받을 때 지정한 <code>Account Resources</code> 스코프가 현재 접속 중인 계정과 일치하는지 재검토해야 합니다.</li>
</ul>
<h3 id="2-forbidden-권한-거부-에러">🚨 2. Forbidden (권한 거부 에러)</h3>
<ul>
<li><strong>원인</strong>: D1 마이그레이션 중에는 에러가 없으나 Pages 배포 또는 KV 리소스 생성 중 에러가 발생하는 경우입니다.</li>
<li><strong>해결</strong>: 생성한 토큰의 Permissions 리스트에 <code>Cloudflare Pages -&gt; Edit</code> 또는 <code>Workers KV Storage -&gt; Edit</code> 권한 중 누락된 권한이 없는지 대시보드 API 토큰 목록에서 확인하고 권한을 추가 수정한 뒤 다시 환경변수 주입을 시도하세요.</li>
</ul>
]]></content:encoded>
            <category>상세 메뉴얼</category>
            <enclosure url="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[SvelteKit Monorepo + Cloudflare 원클릭 배포 및 자동 셋업 상세 설명서]]></title>
            <link>https://fixedblog-3lz.pages.dev/detail-manual/setup-guide</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/detail-manual/setup-guide</guid>
            <pubDate>Mon, 06 Jul 2026 10:23:44 GMT</pubDate>
            <description><![CDATA[npm run setup 명령어를 통해 D1 DB, KV Namespace, Pages 프로젝트 생성부터 스키마 마이그레이션, 비밀 환경 변수 동기화 및 최종 배포까지 모든 과정을 원클릭으로 자동화하는 방식을 깊이 있게 다룹니다.]]></description>
            <content:encoded><![CDATA[<p><img src="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" alt="img-detail-manual-setup-guide-ko-001"></p>
<h1 id="sveltekit-monorepo-cloudflare-원클릭-배포-및-자동-셋업-상세-설명서">🚀 SvelteKit Monorepo + Cloudflare 원클릭 배포 및 자동 셋업 상세 설명서</h1>
<p>본 프로젝트는 스벨트킷(SvelteKit) 모노레포 환경을 기반으로 하며, 서버리스 데이터베이스인 <strong>Cloudflare D1</strong>과 <strong>KV Namespace</strong>, 그리고 <strong>Cloudflare Pages</strong> 배포 인프라를 유기적으로 사용합니다.</p>
<p>모노레포의 다중 애플리케이션 구조에서 발생할 수 있는 데이터베이스 연결 유실, 환경 변수 불일치, 수동 CLI 바인딩 설정 복잡성을 최소화하기 위해 <strong>원클릭 자동화 셋업 스크립트</strong>가 기본 내장되어 있습니다. 이 가이드는 스크립트 실행 전 준비해야 하는 로컬 환경 설정부터 실행 시 주어지는 터미널 선택 프롬프트의 의미와 모드별 활용법을 상세히 소개합니다.</p>
<hr>
<h2 id="1-스크립트-실행-전-사전-준비사항">📂 1. 스크립트 실행 전 사전 준비사항</h2>
<p>셋업 스크립트는 실행 중 불필요한 오류로 작동이 멈추는 것을 방지하기 위해 <strong>Step 0 단계</strong>에서 필수 로컬 환경 변수(<code>.dev.vars</code>)가 올바르게 준비되었는지 사전 검증합니다. 스크립트를 구동하기 전에 반드시 아래의 조치를 완료해야 합니다.</p>
<h3 id="1-1-devvars-파일-생성-및-필수-값-설정">🔑 1-1. <code>.dev.vars</code> 파일 생성 및 필수 값 설정</h3>
<p>프로젝트 루트 기준, 블로그 앱과 어드민 앱 각각의 경로에 <code>.dev.vars.example</code> 파일을 복사하여 <code>.dev.vars</code> 파일을 생성하고 적절한 비밀값을 기입해 주세요.</p>
<ol>
<li><p><strong>블로그 서비스 환경 변수</strong></p>
<ul>
<li><strong>위치</strong>: <code>apps/blog/.dev.vars</code></li>
<li><strong>필수 설정 항목</strong>:<pre><code class="language-env">BETTER_AUTH_SECRET=&quot;임의의_긴_비밀_문자열&quot;
</code></pre>
<em>(인증 세션 및 토큰을 암호화하기 위해 사용되는 유일하고 복잡한 비밀 문자열을 기입합니다. 비어있거나 따옴표만 존재하면 검증이 실패합니다.)</em></li>
</ul>
</li>
<li><p><strong>어드민 서비스 환경 변수</strong></p>
<ul>
<li><strong>위치</strong>: <code>apps/admin/.dev.vars</code></li>
<li><strong>필수 설정 항목</strong>:<pre><code class="language-env">ADMIN_PASSWORD=&quot;어드민_접속_비밀번호&quot;
</code></pre>
<em>(어드민 대시보드 로그인 시 사용할 비밀번호를 기입합니다.)</em></li>
<li><strong>GA4 및 애드센스(AdSense) 더미 데이터 보기</strong>:<br>어드민 대시보드 메인 화면에서 Google Analytics 4(GA4) 방문자 지표 및 Google AdSense 실적 리포트의 **더미 데이터(시뮬레이션 예시)**를 확인하여 화면 출력을 테스트하고 싶다면, 로컬 <code>.dev.vars</code> 파일 및 원격 Cloudflare Pages 설정에서 구글 연동 관련 환경변수들(<code>GA4_PROPERTY_ID</code>, <code>ADSENSE_ACCOUNT_ID</code> 등)을 **등록하지 않거나 완전히 삭제(혹은 주석 처리)**해야 합니다. 관련 필수 변수 중 하나라도 비어 있으면 시스템이 자동으로 더미 데이터를 생성해 출력합니다.</li>
</ul>
</li>
</ol>
<blockquote>
<p>[!WARNING]<br>만약 파일이 없거나 필수 값이 비어 있다면 터미널에 아래와 같은 경고 메시지가 출력되며 셋업이 중단됩니다.</p>
<pre><code class="language-text">❌ apps/admin/.dev.vars 파일이 존재하지 않습니다.
   (apps/admin/.dev.vars.example 파일을 복사하여 생성하고 ADMIN_PASSWORD를 설정해주세요.)
</code></pre>
</blockquote>
<hr>
<h2 id="2-명령어-종류-및-실행-방법">🛠️ 2. 명령어 종류 및 실행 방법</h2>
<table>
<thead>
<tr>
<th align="left">명령어</th>
<th align="left">실행 목적</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>npm run setup</code></td>
<td align="left"><strong>기본 전체 자동 설치 및 배포</strong></td>
<td align="left">초기 기본 언어만 설정하면 리소스 생성부터 최종 빌드/배포까지 논스톱으로 자동 진행됩니다.</td>
</tr>
<tr>
<td align="left"><code>npm run setup:select</code></td>
<td align="left"><strong>사용자 지정 직접 선택 설정</strong></td>
<td align="left">배포 프로젝트명, 데이터 유지/삭제 여부, 리소스명 변경, CDN 캐시 퍼지 연동 등을 조율합니다.</td>
</tr>
<tr>
<td align="left"><code>npm run restore</code></td>
<td align="left"><strong>백업 복원 및 보안 동기화</strong></td>
<td align="left"><code>wrangler.backup.json</code> 백업 파일을 참조하여 인프라를 안전하게 재정렬하고 배포합니다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-실행-단계별-터미널-선택-가이드-prompt-guide">💬 3. 실행 단계별 터미널 선택 가이드 (Prompt Guide)</h2>
<p>스크립트 실행 중 터미널 화면에 나타나는 실제 한글/영어 선택 프롬프트와 각 선택지가 인프라에 미치는 영향에 대한 가이드입니다.</p>
<h3 id="3-1-공통-초기-프롬프트-npm-run-setup-amp-setupselect-공통">🌐 3-1. 공통 초기 프롬프트 (npm run setup &amp; setup:select 공통)</h3>
<h4 id="q1-기본-블로그-언어-선택-기본-콘텐츠-언어-설정">Q1. 기본 블로그 언어 선택 (기본 콘텐츠 언어 설정)</h4>
<p>D1 데이터베이스에 최초로 주입될 초기 가이드 게시글 및 기본 데이터의 언어를 결정합니다.</p>
<pre><code class="language-text">  [EN] Select default blog language:
  [KO] 기본 블로그 언어를 선택하세요:
  1) Korean / 한국어 (ko) [Default]
  2) English / 영어 (en)
  3) Japanese / 日本語 (ja)
  [EN] Choose option (1/2/3) [Default: 1]
  [KO] 옵션을 선택하세요 (1/2/3) [기본값: 1]
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 기본값인 <code>1</code>을 선택하면 한국어로 구성된 기본 시드 데이터가 생성되며, <code>2</code> 또는 <code>3</code> 입력 시 각각 영어/일본어로 번역된 초기 가이드 데이터가 주입됩니다.</li>
</ul>
<h4 id="q2-진행-방식-모드-선택">Q2. 진행 방식 모드 선택</h4>
<p>남은 셋업 과정을 자동으로 완전히 실행할지, 아니면 하나씩 커스텀하여 입력할지 결정합니다.</p>
<pre><code class="language-text">  [EN] Choose setup mode for the remaining steps:
  [KO] 나머지 설정의 진행 방식을 선택하세요:
  1) Full Auto Setup &amp; Deploy / 풀 자동 설정 및 배포 [Default]
  2) Custom Interactive Setup / 사용자 지정 직접 선택 설정
  [EN] Choose option (1/2) [Default: 1]
  [KO] 옵션을 선택하세요 (1/2) [기본값: 1]
  &gt; 
</code></pre>
<ul>
<li><strong>1) Full Auto Setup &amp; Deploy (기본값)</strong>: 블로그 및 어드민 배포명을 기본 규격에 맞춰 생성하고, Cloudflare 로그인 상태만 검증한 뒤 모든 리소스 바인딩과 마이그레이션, 배포를 일사천리로 마칩니다.</li>
<li><strong>2) Custom Interactive Setup</strong>: 상세 커스텀 설정 모드로 전환되며, 아래의 추가 질문들(Q3 ~ Q8)이 연이어 제공됩니다. (<code>npm run setup:select</code> 명령어 실행 시 이 모드로 바로 진입합니다.)</li>
</ul>
<hr>
<h3 id="3-2-상세-커스텀-설정-프롬프트-npm-run-setupselect-전용">🎨 3-2. 상세 커스텀 설정 프롬프트 (<code>npm run setup:select</code> 전용)</h3>
<blockquote>
<p>[!TIP]<br>상세 커스텀 설정 과정에서 나타나는 모든 질문 프롬프트는 값을 별도로 기입하지 않고 <strong>그냥 엔터(Enter)만 쳐서 넘어가도</strong> 대괄호 <code>[Default: ...]</code> 안에 적힌 안전한 기본값으로 자동 설정되므로 간편하게 진행할 수 있습니다.</p>
</blockquote>
<h4 id="q3-배포-프로젝트명-커스텀">Q3. 배포 프로젝트명 커스텀</h4>
<p>Cloudflare Pages 상에 고유하게 생성될 배포 프로젝트 이름을 지정합니다. (기본 도메인은 <code>https://[프로젝트명].pages.dev</code> 형태로 자동 설정됩니다.)</p>
<pre><code class="language-text">  [EN] Enter Blog project name [Default: svelteblog]
  [KO] 블로그 배포명을 입력하세요
  &gt; 

  [EN] Enter Admin project name [Default: svelteadmin]
  [KO] 어드민 배포명을 입력하세요
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 공백 상태로 엔터를 누르면 대괄호 내의 기본 이름이 사용됩니다. 본인의 Cloudflare 계정 내 타 프로젝트와 명칭이 충돌되지 않는 고유한 이름을 사용하고 싶다면 수동으로 기입합니다. (입력 시 <code>package.json</code>의 <code>deploy:blog</code>, <code>deploy:admin</code> 스크립트도 해당 프로젝트 명으로 동적 자동 수정됩니다.)</li>
</ul>
<h4 id="q4-cloudflare-계정-로그인-진행-여부">Q4. Cloudflare 계정 로그인 진행 여부</h4>
<p>Cloudflare API 제어를 위한 인증 과정을 실행합니다.</p>
<pre><code class="language-text">  [EN] Run wrangler login?
  [KO] Wrangler 로그인을 진행할까요?
  [EN] (Y/n) [Default: Y]
  [KO] (Y/n) [기본값: Y]
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 로그인이 되어 있지 않다면 브라우저를 띄워 Cloudflare 계정 연동을 진행합니다. 이미 유효한 로그인이 감지된 상태라면 스크립트가 알아서 해당 세션을 파악하고 넘어가지만, 다른 계정으로 다시 로그인하고 싶다면 <code>Y</code>를 입력할 수 있습니다.</li>
</ul>
<h4 id="q5-데이터-초기화-모드-설정-fresh-vs-keep">Q5. 데이터 초기화 모드 설정 (Fresh vs Keep)</h4>
<p>기존에 구축하여 사용 중이던 Cloudflare 리소스(D1, KV)의 보존 여부를 묻습니다.</p>
<pre><code class="language-text">  [EN] Select Initialization Mode:
    1: Fresh Install (DELETE existing Cloudflare D1/KV databases and start clean)
    2: Keep Existing Data (Use existing Cloudflare databases to preserve your posts/data)
  [KO] 초기화 모드 선택:
    1: 완전 초기화 (기존 클라우드플레어의 D1/KV 데이터베이스를 모두 삭제하고 새로 생성합니다. 데이터 유실 주의!)
    2: 기존 데이터 유지 (기존에 구축된 클라우드플레어 리소스를 그대로 유지하고 연동합니다.)

  [EN] Choose an option (1/2) [Default: 2]
  [KO] 옵션을 선택하세요 (1/2) [기본값: 2]
  &gt; 
</code></pre>
<ul>
<li><strong>Fresh Install (1)</strong>: 기존 Cloudflare에 올라간 D1 DB와 KV Namespace 데이터를 <strong>완전히 영구 삭제</strong>하고 새로 설치합니다. 테스트 중 완전히 빈 상태로 되돌리고 싶을 때만 선택해야 합니다.</li>
<li><strong>Keep Existing Data (2, 권장/기본값)</strong>: 이미 데이터베이스에 등록된 게시글, 회원 등의 데이터를 유지한 채로 새 배포 환경 정보 바인딩만 연결합니다.</li>
</ul>
<h4 id="q6-cloudflare-리소스명-직접-지정">Q6. Cloudflare 리소스명 직접 지정</h4>
<p>Cloudflare 대시보드에 실제로 표시될 D1 및 KV 인스턴스의 이름을 지정합니다.</p>
<pre><code class="language-text">  [EN] Enter Blog DB name [Default: blog-db-xxxxxx]
  [KO] 블로그 DB 이름을 입력하세요
  &gt; 

  [EN] Enter User DB name [Default: user-db-xxxxxx]
  [KO] 유저 DB 이름을 입력하세요
  &gt; 

  [EN] Enter Images KV name [Default: blog-images-kv-xxxxxx]
  [KO] 이미지 KV 이름을 입력하세요
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 별도로 고정하여 관리하고자 하는 DB 명칭이 있다면 입력해 주시고, 그렇지 않다면 그대로 엔터를 눌러 랜덤 서픽스(<code>xxxxxx</code>)가 부여된 고유 이름으로 자동 생성되도록 합니다.</li>
</ul>
<h4 id="q7-cloudflare-cdn-캐시-퍼지-등록">Q7. Cloudflare CDN 캐시 퍼지 등록</h4>
<p>블로그 게시글 수정/삭제 시 배포 도메인의 에지(Edge) 캐시를 즉시 무효화하여 독자에게 최신 본문을 즉시 서빙하기 위한 CDN 동기화 인증 수단을 등록합니다.</p>
<pre><code class="language-text">  [EN] Configure Cloudflare CDN cache purge credentials?
  [KO] CDN 캐시 퍼지 설정을 등록하시겠습니까?
  [EN] (y/N) [Default: N]
  [KO] (y/N) [기본값: N]
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: <code>y</code>를 누르면 Cloudflare 대시보드 화면 우측 하단에서 획득 가능한 <code>Zone ID</code>와 <code>API Token</code> 입력 단계가 시작됩니다. 입력 시 로컬 <code>.dev.vars</code> 파일에 해당 키값이 안전하게 주입되고 Cloudflare Pages의 환경변수로도 벌크 자동 등록됩니다.</li>
</ul>
<h4 id="q8-빌드-및-즉시-배포-여부">Q8. 빌드 및 즉시 배포 여부</h4>
<p>인프라 설정 완료 후 즉시 프로젝트 빌드 및 Cloudflare Pages 서버로 배포 전송을 수행할지 결정합니다.</p>
<pre><code class="language-text">  [EN] Deploy directly?
  [KO] 지금 바로 배포하시겠습니까?
  [EN] (Y/n) [Default: Y]
  [KO] (Y/n) [기본값: Y]
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 로컬 설정을 완료하고 즉시 실 배포 사이트에 반영하고 싶다면 <code>Y</code>를 누릅니다. 빌드가 진행된 뒤 최종 서비스 도메인 URL이 화면에 출력됩니다. 만약 리소스 바인딩 파일(<code>wrangler.json</code>)만 세팅하고 싶다면 <code>n</code>을 입력합니다.</li>
</ul>
<hr>
<h3 id="3-3-배포-동기화-시-조건별-추가-프롬프트-ip-및-다국어-사전">🔒 3-3. 배포 동기화 시 조건별 추가 프롬프트 (IP 및 다국어 사전)</h3>
<p>빌드 및 원격 배포 단계(<code>sync-secrets.js</code>)에 진입하면 추가적으로 관리 환경 보안과 번역 사전 동기화를 위한 판단을 요청받게 됩니다.</p>
<h4 id="q9-어드민-접속-ip-불일치-감지-및-처리">Q9. 어드민 접속 IP 불일치 감지 및 처리</h4>
<p>어드민 서비스는 외부 공격을 차단하기 위해 화이트리스트 기반의 IP 접근 제어(<code>ALLOWED_IP</code>)를 사용합니다. 스크립트가 로컬 PC의 외부 퍼블릭 IP 주소를 실시간 자동 추적하여 기존 설정값과 비교하고 불일치 시 아래의 질문을 던집니다.</p>
<pre><code class="language-text">  1) Keep existing allowed IPs / 기존 목록 유지 (현재 기기 미등록) [Default]
  2) Add current machine IP to list / 현재 기기 IP를 목록에 추가 (복수 허용)
  3) Replace with current machine IP / 현재 기기 IP로만 갱신 (기존 목록 제거)

  [EN] Select option (1/2/3) [Default: 1]
  [KO] 옵션을 선택하세요 (1/2/3) [기본값: 1]
  &gt; 
</code></pre>
<ul>
<li><strong>1) Keep existing allowed IPs (기본값)</strong>: 현재 배포 기기의 IP를 어드민 접속 허용 목록에 추가하지 않고 기존 설정대로 둡니다. (만약 현재 사용 중인 인터넷망이 다른 곳이라면 배포 후 어드민 접속이 차단될 수 있습니다.)</li>
<li><strong>2) Add current machine IP to list</strong>: 기존에 입력되어 있던 다른 주소들을 콤마(<code>,</code>) 구분자로 남겨둔 채, 현재 배포 기기의 퍼블릭 IP를 허용 목록에 <strong>추가</strong>합니다. 여러 장소(예: 집, 사무실)에서 동시에 접근하고자 할 때 최적의 옵션입니다.</li>
<li><strong>3) Replace with current machine IP</strong>: 기존에 등록되었던 허용 목록을 싹 지우고, 오직 현재 접속 중인 이 기기의 외부 IP 주소로만 화이트리스트를 갱신합니다. 단일 환경 보안을 극대화할 때 유용합니다.</li>
</ul>
<h4 id="q10-데이터베이스-다국어-번역-사전ui-dictionary-갱신-여부">Q10. 데이터베이스 다국어 번역 사전(ui_dictionary) 갱신 여부</h4>
<p>어드민 앱 배포 시, 로컬 공유 모듈(<code>packages/shared/src/i18n/index.ts</code>)에 개발자가 정의한 다국어 기본 사전 개체(<code>fallbackDictionary</code>)를 원격 D1 데이터베이스의 <code>blog_settings</code> 테이블에 덮어쓸지 확인합니다.</p>
<pre><code class="language-text">[EN] Force sync remote D1 ui_dictionary with local index.ts? (Web changes will be lost)
[KO] D1 DB의 ui_dictionary를 로컬 index.ts 기준으로 강제 덮어쓰시겠습니까? (웹 수정본 유실 주의)
  [EN] Select option (y/N) [Default: N]
  [KO] 옵션을 선택하세요 (y/N) [기본값: N]
  &gt; 
</code></pre>
<ul>
<li><strong>가이드</strong>: 웹 어드민 화면 내의 &#39;설정 대시보드&#39; 등에서 사용자가 직접 번역 문구를 수정한 이력이 있다면, 로컬 소스코드로 덮어쓰는 순간 웹 수정본이 <strong>유실</strong>될 수 있으므로 <code>N</code>을 권장합니다. 로컬 소스코드의 번역 데이터 수정본을 강제로 원격 서버에 동기화하고자 할 때만 <code>y</code>를 누릅니다. (단, <code>npm run setup</code> 초기 실행 단계에서는 데이터 정합성을 위해 자동 동기화 처리됩니다.)</li>
</ul>
<hr>
<h2 id="4-백업-및-복원-메커니즘-npm-run-restore">🔄 4. 백업 및 복원 메커니즘 (<code>npm run restore</code>)</h2>
<p>셋업이 성공적으로 끝나면 프로젝트 루트에 <code>wrangler.backup.json</code> 파일이 자동 생성됩니다.</p>
<pre><code class="language-json">{
    &quot;d1&quot;: {
        &quot;BLOG_DB&quot;: { &quot;name&quot;: &quot;blog-db-xxxxxx&quot;, &quot;id&quot;: &quot;d1-uuid-value&quot; },
        &quot;USER_DB&quot;: { &quot;name&quot;: &quot;user-db-xxxxxx&quot;, &quot;id&quot;: &quot;d1-uuid-value&quot; }
    },
    &quot;kv&quot;: {
        &quot;IMAGES_KV&quot;: &quot;kv-namespace-id&quot;
    },
    &quot;blogProjectName&quot;: &quot;svelteblog&quot;,
    &quot;adminProjectName&quot;: &quot;svelteadmin&quot;
}
</code></pre>
<p>이 백업 파일이 존재하면 향후 코드 수정이나 기기 변경 시 <code>npm run restore</code> 명령어를 사용해 복구 모드를 진행할 수 있습니다.</p>
<ul>
<li>복원 모드에서는 휴먼 에러 방지를 위해 프로젝트 배포명과 리소스 ID 바인딩이 <strong>완전 자동 고정</strong>되어 즉각 연동됩니다.</li>
<li>또한 기존 데이터를 절대 건드리지 않도록 <strong>초기 시드 주입(Seeding) 단계를 자동으로 안전하게 생략</strong>하므로 기존 운영 중인 블로그 데이터 유실 없이 인프라 관계만 재생성해냅니다.</li>
</ul>
<hr>
<h2 id="5-자주-겪는-문제-및-문제-해결-troubleshooting">❓ 5. 자주 겪는 문제 및 문제 해결 (Troubleshooting)</h2>
<h3 id="1-wrangler-whoami-세션-및-권한-오류">🚨 1. Wrangler Whoami 세션 및 권한 오류</h3>
<ul>
<li><strong>현상</strong>: 스크립트 실행 중 <code>Authentication Error</code> 혹은 D1/KV 리소스 생성 명령어 시점에 세션 없음 에러가 표시되는 경우.</li>
<li><strong>해결</strong>: <code>npm run setup:select</code>를 구동하고 <strong>Q4. Wrangler 로그인</strong> 단계에서 <code>Y</code>를 입력해 브라우저를 통해 재인증을 마쳐야 합니다. 스크립트는 계정 보안을 위해 오직 최소 권한(<code>account:read</code>, <code>user:read</code>, <code>workers:write</code>, <code>pages:write</code>, <code>d1:write</code>, <code>workers_kv:write</code>)만을 요구하므로 안심하고 승인하시면 됩니다.</li>
</ul>
<h3 id="2-windows-환경의-스크립트-실행-권한-차단">🚨 2. Windows 환경의 스크립트 실행 권한 차단</h3>
<ul>
<li><strong>현상</strong>: 윈도우 파워쉘(PowerShell) 등에서 스크립트 무단 실행 정책 오류가 표시되는 경우.</li>
<li><strong>해결</strong>: 셋업 스크립트는 윈도우 OS 감지 시 자동으로 <code>npx</code> 대신 <code>npx.cmd</code>를 호출하고 쉘 제어 플래그를 변환하도록 자체 예외 설계되어 있습니다. 가급적이면 파워쉘보다 <strong>Git Bash</strong> 혹은 <strong>CMD</strong> 터미널을 사용하여 실행하는 것을 권장합니다.</li>
</ul>
]]></content:encoded>
            <category>상세 메뉴얼</category>
            <enclosure url="https://sveltekitblog.com/images/posts/setup-guide/desktop/img-detail-manual-setup-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[블로그 및 관리자 기능 통합 소개]]></title>
            <link>https://fixedblog-3lz.pages.dev/general-guide/general-integration-guide</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/general-guide/general-integration-guide</guid>
            <pubDate>Tue, 16 Jun 2026 15:10:13 GMT</pubDate>
            <description><![CDATA[블로그 앱의 레이아웃과 소통 기능, 그리고 관리자 모드의 다국어 글쓰기 및 실시간 디자인 설정 등 시스템이 제공하는 주요 기능 문서들을 통합 소개합니다.]]></description>
            <content:encoded><![CDATA[<p><img src="https://sveltekitblog.com/images/posts/general-integration-guide/desktop/img-general-guide-general-integration-guide-ko-001.webp" alt="img-general-guide-general-integration-guide-ko-001"></p>
<h1 id="블로그-및-관리자-기능-통합-소개">📖 블로그 및 관리자 기능 통합 소개</h1>
<p>본 문서는 블로그 서비스 및 관리자 기능(어드민)의 핵심 구성을 한눈에 파악할 수 있도록 개별 소개 문서들을 연계하여 안내합니다.</p>
<p><em>참고: 각 기능의 세부적인 연동 방법 및 상세 매뉴얼은 추후 별도 포스팅을 통해 순차적으로 안내될 예정입니다.</em></p>
<hr>
<h2 id="사용자-기능-소개-목록">📂 사용자 기능 소개 목록</h2>
<ul>
<li><strong>관리자(어드민) 기능 소개</strong><ul>
<li><strong><a href="../admin-guide/admin-install-and-deploy">CMD 원클릭 설치 및 클라우드플레어 배포 가이드</a></strong>: 터미널 명령어 한 줄로 클라우드플레어 에지 인프라를 구축하고 블로그를 배포하는 방법을 안내합니다.</li>
<li><strong><a href="../admin-guide/admin-getting-started">어드민 최초 진입 및 환경설정 시작하기</a></strong>: 마스터 비밀번호 로그인, 접속 허용 IP(화이트리스트) 관리 및 다국어 UI 사전 환경설정 개요를 다룹니다.</li>
<li><strong><a href="../admin-guide/admin-core-features">어드민 핵심 기능 및 듀얼 에디터 소개</a></strong>: HTML 비주얼 에디터와 마크다운 에디터를 전환하며 글을 쓰고, 기기별 레이아웃 위젯을 배치하는 방식을 소개합니다.</li>
<li><strong><a href="../admin-guide/admin-design-editor">실시간 디자인 에디터 및 배경 연출 소개</a></strong>: 테마 색상, 단색/그라디언트/배경 이미지/캔버스 스크립트 등 4가지 배경 설정을 재빌드 없이 실시간 반영하는 기능을 소개합니다.</li>
<li><strong><a href="../admin-guide/admin-faq">관리자 자주 묻는 질문 (FAQ) 및 트러블슈팅</a></strong>: 버전 업그레이드, 이미지 저장소 키 점검, 배포 후 오작동 해결 및 IP 보안 차단 해제법 등을 확인합니다.</li>
</ul>
</li>
<li><strong>블로그 기능 소개</strong><ul>
<li><strong><a href="../blog-guide/blog-getting-started">블로그 첫 화면 구성 및 로그인</a></strong>: 블로그 화면 레이아웃 구성과 이메일/소셜 가입 방식을 소개합니다.</li>
<li><strong><a href="../blog-guide/blog-core-features">블로그 핵심 기능 및 다국어 서비스 소개</a></strong>: 다국어 본문 실시간 전환 및 미번역 시 대체 처리 로직, 계층형 댓글과 비밀 방명록을 통한 소통 구조를 다룹니다.</li>
<li><strong><a href="../blog-guide/blog-faq">독자 정책 및 자주 묻는 질문 (FAQ)</a></strong>: 회원 탈퇴 시 작성 데이터 처리 원칙, 재가입 규정 및 차단(밴)/강퇴 조치에 관해 확인합니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="블로그-관리-기본-프로세스">🚀 블로그 관리 기본 프로세스</h2>
<p>블로그 관리자 권한을 설정하고 글 작성을 시작하는 기본 흐름의 개요입니다.</p>
<h3 id="1-ip-허용-목록-적용-및-로그인">1. IP 허용 목록 적용 및 로그인</h3>
<ul>
<li>처음 관리자 화면에 접근 시 보안을 위해 본인의 인터넷 공인 IP가 허용 목록(<code>ALLOWED_IP</code>)에 등록되어 있어야 접근이 허가됩니다. (미등록 시 403 Forbidden 페이지가 표시됩니다.)</li>
<li>로그인 화면에서 설정해 둔 마스터 비밀번호를 입력해 접속합니다.</li>
</ul>
<h3 id="2-다국어-글-일괄-저장">2. 다국어 글 일괄 저장</h3>
<ul>
<li>글쓰기 메뉴에서 제공되는 다국어 탭을 전환하며 본문을 각각 기재한 후, 하단 저장 버튼을 클릭해 데이터베이스에 다국어 글 묶음을 일괄 저장합니다.</li>
</ul>
<h3 id="3-실시간-테마-적용">3. 실시간 테마 적용</h3>
<ul>
<li>디자인 에디터에서 테마 색상, 폰트 종류, 배경 효과(Canvas, 이미지 등)를 저장하면 재빌드 과정 없이 방문자 브라우저 화면에 실시간으로 즉시 적용됩니다.</li>
</ul>
<hr>
<h2 id="마치며">💬 마치며</h2>
<p>본 문서에서 소개한 내용은 블로그와 어드민이 제공하는 기능들의 <strong>간단한 개요</strong>입니다. 실제로는 여기서 미처 다루지 못한 다양한 세부 기능과 편의 옵션들이 더 많이 준비되어 있으며, 직접 사용해 보시면서 하나씩 발견해 나가는 것도 색다른 재미가 될 것입니다.</p>
<p>앞으로 각 기능별 상세 사용법과 활용 팁을 담은 심화 가이드도 순차적으로 포스팅할 예정이니, 필요할 때 다시 방문해 주시면 감사하겠습니다. 이용 중 궁금한 점이나 문의 사항이 있으시다면, 방명록이나 댓글을 통해 언제든 편하게 남겨 주세요.</p>
]]></content:encoded>
            <category>통합 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/general-integration-guide/desktop/img-general-guide-general-integration-guide-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[독자 정책 및 자주 묻는 질문 (FAQ)]]></title>
            <link>https://fixedblog-3lz.pages.dev/blog-guide/blog-faq</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/blog-guide/blog-faq</guid>
            <pubDate>Tue, 16 Jun 2026 15:09:13 GMT</pubDate>
            <description><![CDATA[회원 탈퇴 시의 작성 데이터(댓글, 방명록) 처리 정책 및 이용 제한 정책의 기본 원칙을 소개합니다.]]></description>
            <content:encoded><![CDATA[<p><img src="https://sveltekitblog.com/images/posts/blog-faq/desktop/img-blog-guide-blog-faq-ko-001.webp" alt="img-blog-guide-blog-faq-ko-001"></p>
<h1 id="독자-정책-및-자주-묻는-질문-faq">❓ 독자 정책 및 자주 묻는 질문 (FAQ)</h1>
<p>본 문서는 블로그 서비스 이용 시 발생할 수 있는 회원 탈퇴에 따른 개인정보 및 작성 데이터 처리 방식, 차단(밴) 및 강제 탈퇴(강퇴) 정책 등의 기본 원칙을 소개합니다.</p>
<hr>
<h2 id="q1-회원-탈퇴-시-기존에-작성했던-댓글과-방명록은-어떻게-되나요">🚪 Q1. 회원 탈퇴 시 기존에 작성했던 댓글과 방명록은 어떻게 되나요?</h2>
<p>블로그 시스템은 독자분의 개인정보 보호와 사이트 내 대화 흐름의 무결성을 동시에 유지하기 위해 **&#39;작성자 익명화 처리 정책&#39;**을 적용하고 있습니다.</p>
<h3 id="탈퇴-시-개인정보-처리-원칙">💡 탈퇴 시 개인정보 처리 원칙</h3>
<ol>
<li><strong>활성 로그인 계정 및 인증 정보 파기</strong>:<ul>
<li>회원 탈퇴 진행 시 실제 로그인을 위한 활성 세션과 소셜 로그인 연동 정보는 인증 시스템에서 즉시 안전하게 파기됩니다. 다만, 부정 가입 및 부정 이용 방지, 보안 감사(Audit) 목적으로 탈퇴 당시의 이메일 및 소셜 ID 연동 정보가 격리된 내부 탈퇴 로그 테이블(<code>deleted_users</code>)에 백업 보관됩니다. 이 로그 정보는 시스템 내부 관리용이며, 외부 화면상에서는 작성자 추적이 불가능하도록 완전히 식별 차단 및 익명화(알 수 없음) 처리됩니다.</li>
</ul>
</li>
<li><strong>작성 데이터 보존</strong>:<ul>
<li>작성하셨던 본문 댓글과 방명록 글은 기존 토론 맥락과 대화 흐름을 보존하기 위해 텍스트 내용은 화면에 유지됩니다.</li>
</ul>
</li>
<li><strong>닉네임 익명화 처리</strong>:<ul>
<li>작성자 닉네임과 계정 간의 연결 정보가 해제되며, 작성자 표시명은 **&quot;알 수 없음&quot;**으로 자동 일괄 전환됩니다. 이에 따라 이전 작성자가 실제로 누구였인지 시스템적으로 역추적하는 것이 방지됩니다.</li>
</ul>
</li>
</ol>
<ul>
<li><em>참고: 관리자가 회원 정보를 직접 완전히 삭제하는 경우에는 작성하셨던 댓글과 글이 함께 지워질 수 있습니다.</em></li>
</ul>
<hr>
<h2 id="q2-탈퇴-후-즉시-재가입이-가능한가요">🚪 Q2. 탈퇴 후 즉시 재가입이 가능한가요?</h2>
<ul>
<li><strong>자진 탈퇴 시</strong>:<ul>
<li>본인의 의사로 탈퇴를 완료한 경우, 별도의 유예 기간 없이 동일한 이메일 또는 소셜 계정을 사용하여 즉시 재가입 및 신규 이용이 가능합니다.</li>
</ul>
</li>
<li><strong>활동 차단(밴 - Ban) 상태인 경우</strong>:<ul>
<li>운영 정책 위반으로 계정이 차단(Ban)된 동안에는 이미 해당 이메일로 가입된 정보가 데이터베이스에 존재하므로 동일한 이메일로 새로 가입(재가입)하는 것이 원천적으로 불가능합니다.</li>
</ul>
</li>
<li><strong>강제 탈퇴(강퇴 - Kick) 처리가 된 경우</strong>:<ul>
<li>관리자에 의해 계정이 강제 삭제(Hard Delete)된 경우, 인증 테이블에서 기존 정보가 완전히 삭제되므로 동일한 이메일로 즉시 재가입이 가능해집니다.</li>
<li>시스템 내부 탈퇴 로그(<code>deleted_users</code>)에 강퇴 당시의 이메일 및 소셜 ID 정보가 보존되기는 하나, 현재 회원가입 시점에 이 로그를 대조하여 자동으로 가입을 제한하는 시스템적 차단 로직은 구현되어 있지 않습니다.</li>
<li>따라서, 동일 인물의 악의적인 활동 재발을 물리적으로 원천 차단하기 위해서는 계정을 강제 삭제(강퇴)하는 대신 <strong>&#39;영구 차단(밴 - Ban)&#39;</strong> 상태로 계정을 유지해야 합니다. (밴 상태에서는 이메일이 데이터베이스에 남아있어 중복 가입 제약조건에 의해 동일 이메일 재가입이 차단됩니다.)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="q3-활동이-차단밴당하거나-계정이-강제-탈퇴강퇴되었습니다">🚫 Q3. 활동이 차단(밴)당하거나 계정이 강제 탈퇴(강퇴)되었습니다.</h2>
<p>스팸 성격의 광고 유포, 욕설 및 비방 등 운영 정책을 위반하여 관리자로부터 활동 제한 또는 계정 삭제 처리가 적용된 경우의 확인 사항입니다.</p>
<h3 id="제재-수위별-특징-및-확인-사항">💡 제재 수위별 특징 및 확인 사항</h3>
<ol>
<li><strong>활동 차단 (밴 - Ban)</strong>:<ul>
<li><strong>상태</strong>: 기존 로그인 상태 및 회원 정보는 유지되나, 댓글 작성, 대댓글 등록, 방명록 등록 등 사이트 내 모든 작성 권한이 임시 혹은 무기한 정지됩니다.</li>
<li><strong>원인 및 사유 확인</strong>: 차단이 적용되면 댓글 작성 폼 영역에 **&quot;차단된 유저는 글을 작성할 수 없습니다.&quot;**라는 안내 배너가 나타나며, 차단 세부 사유와 정지 만료일(정지 해제 시간)이 명시적으로 노출됩니다.</li>
</ul>
</li>
<li><strong>강제 탈퇴 (강퇴 - Kick/Delete)</strong>:<ul>
<li><strong>상태</strong>: 로그인 계정 자체가 데이터베이스에서 완전히 삭제(Hard Delete)된 상태입니다. </li>
<li><strong>동작</strong>: 강퇴 처리가 완료되는 즉시 활성 로그인 세션이 강제로 종료되며, 이후 로그인을 시도할 경우 가입 이력이 없는 신규 회원(존재하지 않는 계정)으로 안내됩니다. 관리자가 삭제 유형을 어떻게 설정하느냐에 따라 강퇴 시 기존에 작성했던 댓글들이 모두 한꺼번에 강제 폭파되거나, 작성자 이름만 &#39;알 수 없음&#39;으로 익명 처리될 수 있습니다.</li>
</ul>
</li>
</ol>
]]></content:encoded>
            <category>사용자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/blog-faq/desktop/img-blog-guide-blog-faq-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[블로그 핵심 기능 및 다국어 서비스 소개]]></title>
            <link>https://fixedblog-3lz.pages.dev/blog-guide/blog-core-features</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/blog-guide/blog-core-features</guid>
            <pubDate>Tue, 16 Jun 2026 15:08:16 GMT</pubDate>
            <description><![CDATA[다국어 본문 실시간 전환 및 미번역 시 대체 처리 로직, 계층형 댓글 구조와 비밀 방명록 등 블로그 서비스의 주요 사용자 기능을 소개합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="블로그-핵심-기능-및-다국어-서비스-소개">🌐 블로그 핵심 기능 및 다국어 서비스 소개</h1>
<p>본 문서는 일반 방문자가 블로그의 다국어 번역 글을 조회하고, 댓글 및 방명록을 통해 다른 독자들과 이야기를 나누는 핵심 기능의 명세를 소개합니다.</p>
<hr>
<h2 id="1-다국어-본문-실시간-전환-i18n">🌐 1. 다국어 본문 실시간 전환 (i18n)</h2>
<p>이 블로그는 메뉴나 버튼 등의 기본 UI뿐 아니라, <strong>작성된 포스트 본문 내용 자체</strong>도 여러 언어 버전으로 전환하여 읽을 수 있는 실시간 다국어 번역 뷰어를 제공합니다.</p>
<h3 id="동작-방식-및-특징">⚙️ 동작 방식 및 특징</h3>
<ul>
<li><strong>언어별 URL 라우팅</strong>: 상단 지구본 아이콘 또는 글 제목 옆의 언어 선택 버튼을 클릭하면, URL 주소에 언어 접두사(예: <code>/en</code>, <code>/ja</code>)가 붙으며 해당 언어의 전용 페이지로 즉시 라우팅됩니다. 기본 제공되는 언어(한국어, 영어, 일본어) 외에도, 관리자가 다국어 번역 사전(딕셔너리)을 추가 정의하면 새로운 외국어 버전을 제한 없이 추가로 발행할 수 있습니다.</li>
</ul>
<img src="https://sveltekitblog.com/images/posts/blog-core-features/desktop/img-blog-guide-blog-core-features-ko-001.webp" alt="img-blog-guide-blog-core-features-ko-001" data-align="center" />
<img src="https://sveltekitblog.com/images/posts/blog-core-features/desktop/img-blog-guide-blog-core-features-ko-002.webp" alt="img-blog-guide-blog-core-features-ko-002" data-align="center" />
<figure data-align="center"><img src="https://sveltekitblog.com/images/posts/blog-core-features/desktop/img-blog-guide-blog-core-features-ko-003.webp" alt="img-blog-guide-blog-core-features-ko-003" data-align="center" data-caption="다국어로 포스팅된 글은 해당 언어 버튼을 누르는 즉시 번역본 페이지로 이동합니다." /><figcaption>다국어로 포스팅된 글은 해당 언어 버튼을 누르는 즉시 번역본 페이지로 이동합니다.</figcaption></figure><ul>
<li><strong>수동 작성 데이터 로드 (실시간 자동 번역이 아님)</strong>: 시스템이 텍스트를 기계적으로 실시간 자동 번역해 주는 것이 아닙니다. 작성자가 각 언어 탭에 번역된 포스트 본문 데이터를 직접 작성하여 개별 저장해 두면(외부 AI나 번역기로 가공하여 데이터베이스에 각각 등록), 방문자가 선택한 언어에 대응하는 포스트 데이터를 데이터베이스에서 정확하게 호출하여 화면에 렌더링하는 방식입니다.</li>
<li><strong>본문 및 메타데이터 동시 로드</strong>: 단순 텍스트 교체에 그치지 않고, 데이터베이스에 각 언어별로 등록된 제목, 요약, 태그, 본문 HTML이 완전히 선택된 언어의 개별 데이터로 교체되어 출력됩니다.</li>
<li><strong>미번역 글 대체(Fallback) 처리</strong>: 작성자가 특정 언어의 번역 글을 등록하지 않은 경우에는 방문자가 글을 읽지 못하는 상황을 방지하기 위해, 번역이 준비되지 않았다는 안내와 함께 기본 작성 언어(예: 한국어 본문)로 본문 내용을 대체하여 매끄럽게 보여줍니다.</li>
</ul>
<hr>
<h2 id="2-댓글-및-답글대댓글-기능">💬 2. 댓글 및 답글(대댓글) 기능</h2>
<p>각 게시글 하단에는 비로그인 방문자 또는 회원이 의견을 공유할 수 있는 댓글 영역이 배치되어 있습니다.</p>
<ul>
<li><strong>의견 공유</strong>: 로그인한 사용자는 댓글 입력란에 본문을 작성하고 등록하여 즉시 생각을 공유할 수 있습니다.</li>
<li><strong>계층형 답글(대댓글)</strong>: 특정 댓글 하단에 답글을 달 수 있어 토론 흐름을 트리 구조로 한눈에 알아보기 쉽게 정렬합니다.</li>
<li><strong>보안 및 무결성 유지</strong>: 본인이 작성한 댓글은 삭제가 가능합니다. 단, 이미 하위에 대댓글이 달린 댓글을 삭제하는 경우 전체 토론 맥락이 깨지는 것을 방지하기 위해 &quot;삭제된 댓글입니다.&quot;라는 대체 문구로 마스킹 처리되어 계층 구조를 보존합니다.</li>
</ul>
<hr>
<h2 id="3-방명록-및-비밀글-작성">📖 3. 방명록 및 비밀글 작성</h2>
<p>블로그 방명록 공간(<code>Guestbook</code>)을 통해 사이트 개설자와 소통할 수 있는 기능을 제공합니다.</p>
<h3 id="비밀-방명록-기능">⚙️ 비밀 방명록 기능</h3>
<ul>
<li><strong>비밀글 쓰기</strong>: 글 작성 시 <strong>[🔒 비밀글로 작성]</strong> 체크박스를 활성화하면 보안 글 작성이 가능합니다.</li>
<li><strong>노출 권한 차단</strong>: 비밀글로 저장된 방명록은 제3자(다른 방문자 또는 비로그인 타인)의 목록에는 노출되지 않고 완전히 배제됩니다.</li>
<li><strong>안전한 대화</strong>: 오직 해당 방명록을 작성한 본인(로그인 상태)과 사이트 관리자에게만 본문 내용이 보이며, 안전하게 소통을 나눌 수 있습니다.</li>
</ul>
]]></content:encoded>
            <category>사용자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/blog-core-features/desktop/img-blog-guide-blog-core-features-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[블로그 첫 화면 구성 및 로그인 시작하기]]></title>
            <link>https://fixedblog-3lz.pages.dev/blog-guide/blog-getting-started</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/blog-guide/blog-getting-started</guid>
            <pubDate>Tue, 16 Jun 2026 15:04:15 GMT</pubDate>
            <description><![CDATA[블로그 첫 화면의 기본 레이아웃 구성과 회원가입 및 로그인 방식의 개요를 소개합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="블로그-첫-화면-구성-및-로그인-시작하기">🌐 블로그 첫 화면 구성 및 로그인 시작하기</h1>
<p>본 문서는 블로그(<code>apps/blog</code>) 첫 화면의 레이아웃 구성과 기본적인 회원가입 및 로그인 방식을 간략히 소개합니다.</p>
<hr>
<h2 id="1-블로그-화면-구성-및-레이아웃">🎨 1. 블로그 화면 구성 및 레이아웃</h2>
<p>블로그 화면은 방문자가 정보를 신속하게 찾을 수 있도록 직관적인 레이아웃(헤더, 본문, 사이드바, 푸터)으로 설계되어 있습니다.</p>
<ol>
<li><strong>상단 네비게이션 헤더 (Header)</strong>:<ul>
<li><strong>사이트 로고</strong>: 클릭 시 첫 홈화면으로 복귀합니다.</li>
<li><strong>단축 메뉴 바</strong>: 카테고리 바로가기나 외부 채널 연결 등 주요 링크들이 나열됩니다.</li>
<li><strong>다국어 스위칭</strong>: 지구본 아이콘 클릭 시 사이트 전체의 메뉴 언어 및 게시글 번역본이 실시간으로 전환되어 표시됩니다.</li>
</ul>
</li>
<li><strong>사이드바 영역 (Sidebar)</strong>:<ul>
<li>PC 화면에서는 카테고리 목록, 블로그 운영자 프로필 카드, 인기 태그 등이 노출되어 독자의 탐색을 돕습니다.</li>
<li>모바일 기기 접속 시에는 본문 가독성을 위해 자동으로 감춰집니다.</li>
</ul>
</li>
<li><strong>메인 콘텐츠 영역 (Main Content)</strong>:<ul>
<li>최신 발행 게시글 목록과 카테고리 필터가 카드 디자인 형식으로 배치되어 있습니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="2-회원가입-및-로그인">🔑 2. 회원가입 및 로그인</h2>
<p>댓글 작성 및 방명록 참여를 위한 계정 생성과 로그인 기능을 지원합니다.</p>
<h3 id="로그인-및-가입-지원-방식">⚙️ 로그인 및 가입 지원 방식</h3>
<ul>
<li><strong>기본 이메일 로그인/가입 (Default)</strong>:<ul>
<li>설치 직후 기본 활성화되는 로그인 방식입니다. 이메일 주소, 디스플레이 네임(닉네임), 그리고 비밀번호를 등록하여 신규로 가입할 수 있으며, 가입 즉시 자동 로그인이 적용됩니다.</li>
</ul>
</li>
</ul>
<figure data-align="center"><img src="https://sveltekitblog.com/images/posts/blog-getting-started/desktop/img-blog-guide-blog-getting-started-ko-001.webp" alt="img-blog-guide-blog-getting-started-ko-001" data-align="center" data-caption="기본 이메일 로그인 화면" /><figcaption>기본 이메일 로그인 화면</figcaption></figure><ul>
<li><strong>소셜 로그인 (Better-Auth)</strong>:<ul>
<li>내부적으로 Better-Auth 엔진을 탑재하고 있어 구글(Google), 깃허브(GitHub), 카카오, 네이버 등을 포함한 <strong>총 21종의 소셜 프로바이더 로그인</strong>을 지원합니다.</li>
<li>다만, 소셜 로그인은 사이트 구축 후 관리자가 각 소셜 플랫폼에서 획득한 클라이언트 ID 및 비밀키를 설정 환경변수에 수동으로 주입해야만 로그인 화면에 노출되고 작동합니다. (소셜 프로바이더별 구체적인 연동 가이드는 추후 별도 문서로 안내됩니다.)</li>
</ul>
</li>
</ul>
<figure data-align="center"><img src="https://sveltekitblog.com/images/posts/blog-getting-started/desktop/img-blog-guide-blog-getting-started-ko-002.webp" alt="img-blog-guide-blog-getting-started-ko-002" data-align="center" data-caption="소셜 + 이메일 로그인 화면" /><figcaption>소셜 + 이메일 로그인 화면</figcaption></figure><ul>
<li><strong>로그인 상태 표시</strong>:<ul>
<li>로그인이 완료되면 오른쪽 위 버튼이 사용자의 프로필 이미지 아이콘과 마이페이지 바로가기로 변경됩니다.</li>
<li><em>참고: 사용자 프로필 사진을 실제 연동하여 표시하는 기능은 현재 완벽히 작동하지 않을 수 있으며, 기본 이미지 아이콘 등으로 대체 표시될 수 있습니다.</em></li>
</ul>
</li>
</ul>
]]></content:encoded>
            <category>사용자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/blog-getting-started/desktop/img-blog-guide-blog-getting-started-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[어드민 자주 묻는 질문 (FAQ) 및 트러블슈팅]]></title>
            <link>https://fixedblog-3lz.pages.dev/admin-guide/admin-faq</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/admin-guide/admin-faq</guid>
            <pubDate>Tue, 16 Jun 2026 15:01:11 GMT</pubDate>
            <description><![CDATA[설치 및 배포 중 발생하는 오류 해결, 버전 업그레이드, 이미지 저장소 필수 키 리스트 및 IP 보안 차단 해제법을 확인합니다.]]></description>
            <content:encoded><![CDATA[<img src="https://sveltekitblog.com/images/posts/admin-faq/desktop/img-admin-guide-admin-faq-ko-001.webp" alt="img-admin-guide-admin-faq-ko-001" data-align="center"><h1 id="어드민-자주-묻는-질문-faq-및-트러블슈팅">❓ 어드민 자주 묻는 질문 (FAQ) 및 트러블슈팅</h1>
<p>본 문서는 블로그 설치, 배포, 운영 단계에서 발생할 수 있는 주요 트러블슈팅 해결 방안과 새 버전 업그레이드 가이드를 제공합니다.</p>
<hr>
<h2 id="q1-최신-버전으로-안전하게-업그레이드upgrade하거나-데이터를-복원하는-방법을-알려주세요">🗄️ Q1. 최신 버전으로 안전하게 업그레이드(Upgrade)하거나 데이터를 복원하는 방법을 알려주세요.</h2>
<p>블로그 데이터베이스의 정합성을 지키고 예기치 못한 빌드 크래시를 방지하기 위해 아래의 안전한 업그레이드 절차를 준수하는 것을 강력히 권장합니다.</p>
<h3 id="1-안전한-새-버전-업그레이드-검증-절차-권장">1. 안전한 새 버전 업그레이드 검증 절차 (권장)</h3>
<p>기존 DB와의 스키마 불일치나 템플릿 변경에 따른 충돌을 예방하기 위해, 즉시 본 서버에 배포하는 대신 테스트 환경에서 선검증을 거치십시오. (Git 미사용자 포함)</p>
<ol>
<li><strong>설정 백업 보존</strong>:<ul>
<li>기존 개발 폴더 안의 <code>wrangler.backup.json</code> 및 <code>.dev.vars</code> 파일을 안전한 임시 폴더에 복사해 둡니다.</li>
<li><em>※ <code>.dev.vars</code> 파일은 중요 인증 정보(비밀번호, OAuth 등)를 담고 있으나 보안상 Git 관리 대상에서 제외(<code>.gitignore</code>)되어 있으므로, 코드를 새로 가져오거나 초기화할 때 유실되기 쉽습니다. 반드시 백업을 받아두어야 합니다.</em></li>
</ul>
</li>
<li><strong>기존 운영 데이터 백업</strong>:<ul>
<li>현재 가동 중인 블로그 어드민의 <code>콘텐츠 백업</code> 메뉴에서 전체 포스트 및 설정 백업 파일을 다운로드해 둡니다.</li>
</ul>
</li>
<li><strong>격리된 폴더에 새 버전 클론/다운로드</strong>:<ul>
<li>기존 폴더를 덮어쓰지 말고, <strong>완전히 새로운 디렉토리</strong>에 최신 릴리스 소스코드를 내려받거나 클론합니다.</li>
</ul>
</li>
<li><strong>임시 테스트 서버 배포</strong>:<ul>
<li>새 폴더로 이동해 의존성을 설치(<code>npm install</code>)하고 임시 테스트용 데이터베이스를 생성하여 테스트 배포를 수행합니다.</li>
</ul>
</li>
<li><strong>백업 복원 테스트</strong>:<ul>
<li>새로 임시 배포된 테스트 어드민에 접속하여, 2단계에서 다운로드했던 백업 파일을 로드해 데이터가 유실 없이 잘 나타나고 에러가 없는지 꼼꼼하게 검증합니다.</li>
</ul>
</li>
<li><strong>본 서버 정식 업데이트 적용</strong>:<ul>
<li>테스트 서버에서 모든 기능이 정상 작동함을 확인한 뒤에만, 기존 가동 폴더로 돌아와 코드를 갱신(<code>git pull</code> 또는 소스 덮어쓰기)하고 개별 앱 배포 명령어(<code>npm run deploy:blog</code>, <code>npm run deploy:admin</code>)를 실행하여 정식 업그레이드를 마칩니다.</li>
</ul>
</li>
</ol>
<h3 id="2-데이터-유실-시-최후의-복구-수단">2. 데이터 유실 시 최후의 복구 수단</h3>
<ul>
<li><strong><code>npm run restore</code> 명령어 사용</strong>:<ul>
<li>만약 인프라 설정이나 데이터가 심각하게 훼손되어 원천 복구가 필요한 경우에만, 백업해 두었던 <code>wrangler.backup.json</code> 파일을 기반으로 전체 인프라를 복구 재구성하는 <strong>최후의 데이터 복구 수단</strong>으로 실행하십시오.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="q2-r2-supabase-imagekit-이미지-저장소-변경-시-입력해야-할-필수-키는-무엇인가요">📂 Q2. R2, Supabase, ImageKit 이미지 저장소 변경 시 입력해야 할 필수 키는 무엇인가요?</h2>
<p>각 외부 이미지 저장소 사용 시 누락 없이 입력해야 하는 필수 환경변수 및 세팅 체크리스트입니다.</p>
<table>
<thead>
<tr>
<th align="left">저장소 타입</th>
<th align="left">필수 입력 항목 / 설정</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Cloudflare R2</strong></td>
<td align="left"><code>IMAGES</code> R2 버킷 바인딩</td>
<td align="left"><code>wrangler.json</code> 내 R2 버킷 정보와 매핑이 필요합니다.</td>
</tr>
<tr>
<td align="left"><strong>Supabase Storage</strong></td>
<td align="left"><code>supabase_storage_url</code><br><code>supabase_storage_key</code><br><code>supabase_storage_bucket</code></td>
<td align="left">API URL 및 Service Role Key가 정확해야 하며, Supabase Storage 내 버킷 권한 정책이 **Public(공개)**으로 설정되어 있어야 이미지가 깨지지 않습니다.</td>
</tr>
<tr>
<td align="left"><strong>ImageKit.io</strong></td>
<td align="left"><code>imagekit_url_endpoint</code><br><code>imagekit_public_key</code><br><code>imagekit_private_key</code></td>
<td align="left">엔드포인트 URL 형식을 점검하고 도메인 보안(CORS) 허용을 설정해야 합니다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="q3-대시보드-통계-그래프가-데모-데이터로만-표시됩니다">📊 Q3. 대시보드 통계 그래프가 데모 데이터로만 표시됩니다.</h2>
<ul>
<li><strong>원인</strong>: 구글 애널리틱스 4(GA4) API 환경변수가 입력되지 않았거나 비정상적인 경우, 오류 방지를 위해 임시 데모 데이터가 대체 표시됩니다.</li>
<li><strong>설정 방법</strong>: Cloudflare Pages 설정에 아래 환경변수를 입력하고 다시 배포합니다.<ul>
<li><code>GA4_PROPERTY_ID</code>: 구글 애널리틱스 속성 ID</li>
<li><code>GA4_CLIENT_EMAIL</code>: 구글 클라우드 서비스 계정 이메일</li>
<li><code>GA4_PRIVATE_KEY</code>: 구글 서비스 계정 비공개 키</li>
</ul>
</li>
<li><strong>주의 사항</strong>: <code>.dev.vars</code> 파일에 <code>GA4_PRIVATE_KEY</code>를 작성할 때 개행 코드(<code>\n</code>) 형식이 깨지지 않도록 반드시 전체 키 문자열을 큰따옴표(<code>&quot;</code>)로 묶어 등록해야 합니다.</li>
</ul>
<hr>
<h2 id="q4-최초-setup-배포-후-어드민-접속이-안-되거나-일부-데이터-동기화가-누락된-것-같습니다">⚡ Q4. 최초 setup 배포 후 어드민 접속이 안 되거나 일부 데이터 동기화가 누락된 것 같습니다.</h2>
<ul>
<li><strong>원인</strong>: 원클릭 셋업 단계에서 환경변수 설정 유도가 정상적으로 완료되었더라도, 예상치 못한 원격 통신 장애나 기타 시스템 에러로 인해 암호화 키 또는 비밀 환경변수(Secrets)들의 동기화가 일부 누락되거나 오염된 상태로 배포가 진행될 수 있습니다.</li>
<li><strong>해결법</strong>: 각 앱의 <code>.dev.vars</code> 설정값 상태를 다시 한번 육안으로 점검한 후, 각 폴더로 이동해 수동으로 개별 배포 명령어인 <code>npm run deploy:blog</code>와 <code>npm run deploy:admin</code>을 각각 <strong>1회 실행</strong>해 주십시오. 로컬의 <code>.dev.vars</code> 파일에 들어있던 비밀 환경변수들이 정상적으로 덮어씌워지면서 오작동이 해결됩니다.</li>
</ul>
<hr>
<h2 id="q5-어드민-페이지-접근-시-quotforbidden-ip-not-allowedquot-또는-403-에러가-뜹니다">🔒 Q5. 어드민 페이지 접근 시 &quot;Forbidden (IP Not Allowed)&quot; 또는 403 에러가 뜹니다.</h2>
<p>이 블로그의 보안 사양상 배포 스크립트 실행 과정에서 배포 PC의 공인 IP 주소를 자동으로 감지하여 접속 허용 IP(<code>ALLOWED_IP</code>)로 Pages Secret에 자동 주입합니다.</p>
<h3 id="어드민-보안-권고-사항">⚠️ 어드민 보안 권고 사항</h3>
<ul>
<li>어드민 계정 탈취 및 비인가 접속을 예방하기 위해, 외부 공공장소(카페, 도서관 등)나 신뢰성이 보장되지 않는 <strong>공용 PC</strong>에서의 어드민 접속 및 관리 작업은 <strong>보안상 강력히 제한할 것을 권장</strong>합니다.</li>
</ul>
<h3 id="상황별-대처-요령">💡 상황별 대처 요령</h3>
<ul>
<li><strong>상황 A. 집이나 사무실에서 인터넷 공유기가 재부팅되어 IP가 바뀐 경우</strong>:<ul>
<li>기존 작업을 진행하던 집/사무실의 메인 개발 PC 터미널에서 <code>npm run deploy:admin</code>을 1회 실행해주면 변경된 공인 IP를 자동으로 재감지하여 배포가 갱신되면서 즉시 접속이 재개됩니다.</li>
</ul>
</li>
<li><strong>상황 B. 피치 못하게 외부 장소로 개발 환경을 옮겨 장기 포스팅해야 하는 경우</strong>:<ul>
<li>기존 배포 정보가 담긴 백업 설정 파일(<code>wrangler.backup.json</code> 등)을 가지고 새로운 장소의 PC에서 원클릭 셋업 명령어로 새로 빌드/배포를 실행해 접속 권한을 획득하는 방법이 가장 간단합니다.</li>
<li><strong>※ 주의:</strong> 외부 작업을 마친 후 다시 원래 집이나 사무실로 귀환하여 블로그를 관리할 때는, <strong>반드시 기존 본래 개발 PC에서 <code>npm run deploy:admin</code>을 다시 실행하여 허용 IP 주소를 다시 원래 고정 IP로 복원 배포</strong>해주어야 정상 제어가 가능해집니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="q6-better-auth소셜-로그인-및-회원가입-관련-오류나-로그인-무한-루프가-발생합니다">🔐 Q6. Better Auth(소셜 로그인 및 회원가입) 관련 오류나 로그인 무한 루프가 발생합니다.</h2>
<ul>
<li><strong>원인</strong>: 인증 보안에 사용되는 환경변수 정보가 누락되었거나 비정상일 때 세션 검증이 실패하여 계속 로그인 루프에 빠지게 됩니다.</li>
<li><strong>해결법</strong>: <ol>
<li><code>apps/blog/.dev.vars</code> 파일을 열고 <code>BETTER_AUTH_SECRET</code> 키값에 <strong>최소 32자 이상의 안전한 무작위 비밀 문자열</strong>이 정상 입력되었는지 점검합니다.</li>
<li>비밀값을 올바르게 설정한 후 <code>npm run deploy:blog</code>를 실행해 주면 변경된 보안키가 원격 Pages Secret으로 동기화되어 인증 기능이 정상으로 돌아옵니다.</li>
</ol>
</li>
</ul>
]]></content:encoded>
            <category>관리자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/admin-faq/desktop/img-admin-guide-admin-faq-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[실시간 디자인 에디터 및 배경 연출 소개]]></title>
            <link>https://fixedblog-3lz.pages.dev/admin-guide/admin-design-editor</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/admin-guide/admin-design-editor</guid>
            <pubDate>Tue, 16 Jun 2026 14:55:17 GMT</pubDate>
            <description><![CDATA[재빌드와 재배포 없는 실시간 테마 반영]]></description>
            <content:encoded><![CDATA[<h1 id="실시간-디자인-에디터-및-배경-연출-소개">🎨 실시간 디자인 에디터 및 배경 연출 소개</h1>
<p>본 문서는 재빌드 및 재배포 없이 테마 설정을 실시간으로 반영하는 시스템과 4가지 배경 설정(단색, 그라디언트, 배경 이미지, 커스텀 자바스크립트 캔버스)에 대한 기본 구성을 소개합니다.</p>
<hr>
<h2 id="1-실시간-디자인-에디터-개요">🌌 1. 실시간 디자인 에디터 개요</h2>
<p>디자인 에디터에서 설정값을 변경하고 저장할 때 블로그 서버나 CDN 서비스를 다시 빌드하고 배포할 필요가 전혀 없습니다.<br>저장 즉시 데이터베이스가 갱신되며, 방문자의 브라우저 화면에 <strong>CSS 변수와 배경 렌더링 모듈이 실시간으로 반영</strong>됩니다.<br>블로그에 최종적으로 반영하기 전에, 어드민 페이지 내의 프리뷰 화면을 통해 디자인을 미리 가늠해볼 수 있습니다.</p>
<blockquote>
<p>[!WARNING]</p>
<h3 id="프리뷰와-실제-블로그-디자인의-차이점-주의">⚠️ 프리뷰와 실제 블로그 디자인의 차이점 주의</h3>
<p>디자인 에디터의 <strong>프리뷰 기능은 실제 방문자 화면과 100% 동일한 환경을 보장하지 않습니다.</strong> 브라우저의 CSS 변수 해석 방식이나 자바스크립트 캔버스 렌더링 조건 등에 따라 어드민 프리뷰와 실제 블로그 화면에 미세한 차이가 발생할 수 있습니다.</p>
<p>따라서 디자인 설정을 변경한 뒤에는 <strong>반드시 실제 블로그 메인 화면에 접속하여 새로고침(F5)을 통해 최종 연출 상태를 눈으로 검증</strong>하시는 것을 강력히 권장합니다.</p>
</blockquote>
<hr>
<h3 id="레이아웃-및-테마-설정-항목">🧱 레이아웃 및 테마 설정 항목</h3>
<h4 id="레이아웃-구성">① 레이아웃 구성</h4>
<ul>
<li><strong>블로그 기본 구조</strong>: 메인 페이지의 구조(예: 2컬럼, 3컬럼 등)를 선택할 수 있습니다.</li>
<li><strong>컬럼 폭 비율</strong>: 메인 콘텐츠 영역과 각 사이드바 컬럼 간의 너비 비율(예: 1:2:1, 2:1:2 등)을 조절합니다.</li>
<li><strong>화면 최대 가로 폭</strong>: 전체 웹사이트가 확장될 수 있는 최대 가로 해상도(예: 1200px, 1400px 등)를 제한합니다.</li>
<li><strong>세부 레이아웃 수치</strong>: 페이지 좌우 여백(Side Margin), 콘텐츠 카드 모서리 둥글기(Border Radius), 깊이감을 더해주는 그림자(Box Shadow) 효과를 미세 조절합니다.</li>
</ul>
<h4 id="테마-기본-색상-구성">② 테마 기본 색상 구성</h4>
<ul>
<li>주 테마 색상(Primary), 보조 색상(Secondary), 본문 글자 색상(Text), 포인트 강조 색상(Accent), 카드 및 위젯 배경(Card Bg), 그리고 테두리 색상(Border)을 조율합니다.</li>
</ul>
<h4 id="타이포그래피-서체">③ 타이포그래피 (서체)</h4>
<ul>
<li>Google Fonts 서체 목록 중에서 원하는 폰트 패밀리 명칭을 직접 기입하여 적용하고, 본문 폰트 종류와 기본 글자 크기(Base Font Size)를 지정할 수 있습니다.</li>
</ul>
<h4 id="위젯-배치-및-순서-관리">④ 위젯 배치 및 순서 관리</h4>
<ul>
<li>배치하고 싶은 위젯들을 마우스 드래그 앤 드롭으로 각 컬럼에 자유롭게 배치하고 순서를 지정할 수 있습니다.</li>
</ul>
<hr>
<h2 id="2-기기별-독립-위젯-배치">🧱 2. 기기별 독립 위젯 배치</h2>
<p>레이아웃 설계 시 마우스 드래그 앤 드롭으로 위젯의 위치와 순서를 지정할 수 있으며, 접속하는 독자의 기기 크기에 맞춰 레이아웃을 다르게 구성할 수 있는 필터링 기능을 제공합니다.</p>
<ul>
<li><strong>Desktop (데스크톱 전용)</strong>: 화면이 넓은 PC 환경에서만 해당 위젯(예: 태그 클라우드, 카테고리 트리 등)을 출력합니다. 모바일 접속 시에는 HTML 단계에서 요소를 사전에 제외하여 로딩 속도를 유지해 줍니다.</li>
<li><strong>Mobile (모바일 전용)</strong>: 데스크톱에서는 보이지 않고, 스마트폰 화면 크기에서만 모바일 친화적으로 단독 출력되도록 제한합니다.</li>
</ul>
<hr>
<h2 id="3-영역별-배경-설정-종류">🌈 3. 영역별 배경 설정 종류</h2>
<p>블로그의 배경 화면은 <strong>헤더(Header), 메인(Main), 푸터(Footer)</strong> 영역으로 구분하여 각각 독립적으로 디자인을 연출할 수 있습니다. </p>
<ul>
<li><strong>상속(Inheritance) 기능</strong>: 헤더와 푸터의 경우, &#39;상위 설정 상속&#39;을 활성화하여 메인(Main) 영역의 배경을 그대로 이어받도록 간편하게 설정할 수도 있습니다.</li>
<li><strong>4가지 배경 타입</strong>: 영역별로 적용 가능한 배경 화면은 총 4가지 타입을 지원합니다.</li>
</ul>
<h3 id="solid-단색-배경">① Solid (단색 배경)</h3>
<ul>
<li>선호하는 색상의 헥스(HEX) 코드(예: <code>#3b82f6</code>)나 HSL 색상 코드를 지정하여 가장 깔끔하고 포스트 본문에 집중하기 좋은 화면을 만듭니다.</li>
</ul>
<h3 id="gradient-그라디언트-배경">② Gradient (그라디언트 배경)</h3>
<ul>
<li><strong>그라디언트 빌더(Gradient Builder)</strong> 도구를 사용하여 여러 컬러가 자연스럽게 섞이는 선형 그라디언트를 구성합니다.</li>
<li><strong>각도 조절(Direction)</strong> 슬라이더와 **색상 정지점(Stops)**을 마우스로 추가하고 이동하며 시작색, 중간 경유색, 종점색을 배합할 수 있습니다.</li>
</ul>
<h3 id="image-배경-이미지-amp-글래스모피즘-필터-조합">③ Image (배경 이미지 &amp; 글래스모피즘 필터 조합)</h3>
<ul>
<li>배경으로 사용할 사진의 주소(URL)를 입력하거나, 업로드(Upload) 버튼을 사용해 미디어 파일을 보관함에 추가합니다.</li>
<li><strong>업로드 이미지 최적화</strong>: 이미지를 등록하면 시스템이 WebP 형식으로 자동 변환하여 로딩 성능 저하를 방지합니다.</li>
<li><strong>글래스모피즘 효과</strong>: 배경 이미지가 텍스트 가독성을 저해하지 않도록, 아래의 제어 설정을 활용해 반투명 유리창 스타일을 구현할 수 있습니다.</li>
</ul>
<table>
<thead>
<tr>
<th align="left">설정 항목</th>
<th align="center">권장 조작 범위</th>
<th align="left">상세 설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>배경 블러 (Glass Blur)</strong></td>
<td align="center"><code>5px ~ 15px</code></td>
<td align="left">글자가 놓이는 콘텐츠 카드 하단에 투명한 유리 재질 느낌의 필터를 입힙니다.</td>
</tr>
<tr>
<td align="left"><strong>유리창 틴트 농도 (Overlay Opacity)</strong></td>
<td align="center"><code>10% ~ 30%</code></td>
<td align="left">유리창 뒤편에 깔리는 반투명 마스크의 불투명도를 결정합니다.</td>
</tr>
<tr>
<td align="left"><strong>유리창 틴트 색상 (Overlay Color)</strong></td>
<td align="center"><code>#000000</code> 또는 <code>#ffffff</code></td>
<td align="left">마스크 색상을 어둡게(Dark) 혹은 밝게(Light) 연출하여 글자의 선명한 대비(Contrast)를 확보합니다.</td>
</tr>
</tbody></table>
<h3 id="custom-javascript-커스텀-자바스크립트-및-canvas-배경">④ Custom JavaScript (커스텀 자바스크립트 및 Canvas 배경)</h3>
<ul>
<li>방문자 브라우저에서 배경용 Canvas 애니메이션을 직접 구동할 수 있도록 커스텀 자바스크립트 입력을 지원합니다.</li>
</ul>
<figure data-align="center"><img src="https://sveltekitblog.com/images/posts/admin-design-editor/desktop/img-admin-guide-admin-design-editor-ko-001.webp" alt="img-admin-guide-admin-design-editor-ko-001" data-align="center" data-caption="Custom JS 캔버스 애니메이션 입력창 활성화 전" /><figcaption>Custom JS 캔버스 애니메이션 입력창 활성화 전</figcaption></figure>
<figure data-align="center"><img src="https://sveltekitblog.com/images/posts/admin-design-editor/desktop/img-admin-guide-admin-design-editor-ko-002.webp" alt="img-admin-guide-admin-design-editor-ko-002" data-align="center" data-caption="Custom JS 캔버스 애니메이션 입력창 활성화 후" /><figcaption>Custom JS 캔버스 애니메이션 입력창 활성화 후</figcaption></figure><ul>
<li>캔버스 요소(<code>canvas id=&quot;bg-canvas&quot;</code>)에 직접 렌더링을 제어할 수 있으며, 이와 연동되는 세부 보안 및 절전 시스템은 아래 **[4. 커스텀 자바스크립트 연동 사양]**에서 설명합니다.</li>
</ul>
<hr>
<h2 id="4-커스텀-자바스크립트-연동-사양">⚡ 4. 커스텀 자바스크립트 연동 사양</h2>
<p>보안 사고를 예방하고 모바일 기기 배터리를 보존하기 위해 아래와 같은 격리 정책 및 절전 시스템이 적용됩니다. 외부 라이브러리나 프레임워크는 사용할 수 없으며, 오직 순수 바닐라 자바스크립트(Vanilla JS)로만 작성해야 합니다.</p>
<h3 id="격리-보안-시스템-sandbox-amp-csp">🔒 격리 보안 시스템 (Sandbox &amp; CSP)</h3>
<p>블로그 어드민일지라도 악성 스크립트 유입으로 인한 보안 사고를 막기 위해 다음과 같은 보안 정책이 상시 가동됩니다.</p>
<ol>
<li><strong>격리된 Sandbox 구조</strong>: 캔버스 코드는 오직 스크립트 실행만 인가된 격리된 <code>iframe</code> 내부에서 구동됩니다. 부모 페이지의 DOM 노드나 로그인 정보 등에 접근하여 데이터를 탈취하거나 위변조하는 동작이 기술적으로 차단됩니다.</li>
<li><strong>콘텐츠 보안 정책 (CSP) 제한</strong>: 외부 서버와의 네트워크 통신 및 외부 스크립트 호출이 전면 차단됩니다. 따라서 외부 서버로 데이터를 유출하는 것이 불가능합니다.</li>
<li><strong>JS API 허용 차단</strong>: 보안을 해칠 수 있는 <code>fetch</code>, <code>XMLHttpRequest</code>, <code>WebSocket</code>, <code>eval</code>, <code>new Function</code>, <code>document.cookie</code>, <code>localStorage</code> 등의 API가 감지되면 보안 필터(<code>jsValidator.ts</code>)가 이를 자동으로 주석 처리(<code>/* f_e_t_c_h (blocked) */</code>)하여 무력화합니다.</li>
</ol>
<h3 id="배터리-보존-및-절전-기술">🔋 배터리 보존 및 절전 기술</h3>
<ul>
<li><strong>화면 밖 자동 일시정지</strong>: 사용자가 화면을 스크롤하여 배경 애니메이션이 완전히 시야에서 사라지면, 브라우저가 CPU/GPU를 낭비하지 않도록 프레임 렌더링 루프를 <strong>일시정지(Sleep)</strong> 상태로 전환합니다. 화면에 다시 노출되면 즉각 재생(Resume)됩니다.</li>
<li><strong>모바일 프레임 스로틀링</strong>: 스마트폰이나 태블릿 등 모바일 기기로 접속하는 경우 기기 발열 방지를 위해 기본적으로 애니메이션이 정지됩니다. <strong>&quot;모바일 기기에서도 애니메이션 구동&quot;</strong> 옵션을 명시적으로 체크하면 실행할 수 있으며, 이때 성능에 부담이 가지 않도록 파티클 개수를 자동으로 축소 제어하는 매개변수가 연동됩니다.</li>
</ul>
<hr>
<h2 id="5-예제-스크립트-코드-3종">📝 5. 예제 스크립트 코드 3종</h2>
<p>배경 설정 메뉴에서 **[Custom JavaScript]**를 선택하고, 아래 코드 중 하나를 복사하여 적용할 수 있습니다. 추후 몇 가지 예제 코드가 더 추가될 수 있습니다.</p>
<blockquote>
<p>[!NOTE]<br>스크립트를 작성할 때에는 배경을 그릴 대상 캔버스 요소를 찾기 위해 반드시 <code>document.getElementById(&#39;bg-canvas&#39;)</code> 아이디명을 사용하여 컨텍스트를 획득해 주시기 바랍니다.</p>
</blockquote>
<h3 id="예제-a-겨울-눈송이-snowfall">❄️ 예제 A. 겨울 눈송이 (Snowfall)</h3>
<p>화면 위에서 눈송이들이 아래로 천천히 떨어지는 배경 애니메이션입니다.</p>
<pre><code class="language-javascript">(function() {
  const canvas = document.getElementById(&#39;bg-canvas&#39;);
  if (!canvas) return;
  const ctx = canvas.getContext(&#39;2d&#39;);
  
  let width = canvas.width = window.innerWidth;
  let height = canvas.height = window.innerHeight;
  
  // 모바일 스로틀 감지
  const divisor = (window.bgConfig &amp;&amp; window.bgConfig.mobileThrottleDivisor) || 1;
  const maxSnowflakes = Math.floor(100 / divisor);
  const snowflakes = [];
  
  class Snowflake {
    constructor() {
      this.reset();
      this.y = Math.random() * height; // 초기 임의 고도 설정
    }
    
    reset() {
      this.x = Math.random() * width;
      this.y = -10;
      this.radius = Math.random() * 3 + 1;
      this.speed = Math.random() * 1 + 0.5;
      this.opacity = Math.random() * 0.6 + 0.2;
    }
    
    update() {
      this.y += this.speed;
      // 부드러운 흔들림 주기
      this.x += Math.sin(this.y / 30) * 0.5;
      
      if (this.y &gt; height || this.x &lt; 0 || this.x &gt; width) {
        this.reset();
      }
    }
    
    draw() {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(25, 25, 255, ${this.opacity})`;
      ctx.fill();
    }
  }
  
  // 파티클 생성
  for (let i = 0; i &lt; maxSnowflakes; i++) {
    snowflakes.push(new Snowflake());
  }
  
  function animate() {
    ctx.clearRect(0, 0, width, height);
    
    for (let i = 0; i &lt; snowflakes.length; i++) {
      snowflakes[i].update();
      snowflakes[i].draw();
    }
    requestAnimationFrame(animate);
  }
  
  // 창 크기 조절 이벤트
  window.addEventListener(&#39;resize&#39;, () =&gt; {
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
  });
  
  animate();
})();
</code></pre>
<hr>
<h3 id="예제-b-별자리-연결망-constellation-network">🕸️ 예제 B. 별자리 연결망 (Constellation Network)</h3>
<p>파티클 노드들이 무작위로 부유하며, 서로 가까워지면 얇은 선으로 연결되어 그물 형태를 구성하는 배경 애니메이션입니다.</p>
<pre><code class="language-javascript">(function() {
  const canvas = document.getElementById(&#39;bg-canvas&#39;);
  if (!canvas) return;
  const ctx = canvas.getContext(&#39;2d&#39;);
  
  let width = canvas.width = window.innerWidth;
  let height = canvas.height = window.innerHeight;
  
  const divisor = (window.bgConfig &amp;&amp; window.bgConfig.mobileThrottleDivisor) || 1;
  const particleCount = Math.floor(80 / divisor);
  const particles = [];
  const connectionDistance = 100;
  
  class Particle {
    constructor() {
      this.x = Math.random() * width;
      this.y = Math.random() * height;
      this.vx = (Math.random() - 0.5) * 0.8;
      this.vy = (Math.random() - 0.5) * 0.8;
      this.radius = Math.random() * 2 + 1.5;
    }
    
    update() {
      this.x += this.vx;
      this.y += this.vy;
      
      // 화면 경계 튕기기
      if (this.x &lt; 0 || this.x &gt; width) this.vx *= -1;
      if (this.y &lt; 0 || this.y &gt; height) this.vy *= -1;
    }
    
    draw() {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
      ctx.fillStyle = &#39;rgba(99, 102, 241, 0.4)&#39;; // 파스텔 인디고 컬러
      ctx.fill();
    }
  }
  
  for (let i = 0; i &lt; particleCount; i++) {
    particles.push(new Particle());
  }
  
  function drawLines() {
    for (let i = 0; i &lt; particles.length; i++) {
      for (let j = i + 1; j &lt; particles.length; j++) {
        const dx = particles[i].x - particles[j].x;
        const dy = particles[i].y - particles[j].y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        
        if (dist &lt; connectionDistance) {
          const alpha = (connectionDistance - dist) / connectionDistance * 0.18;
          ctx.beginPath();
          ctx.moveTo(particles[i].x, particles[i].y);
          ctx.lineTo(particles[j].x, particles[j].y);
          ctx.strokeStyle = `rgba(99, 102, 241, ${alpha})`;
          ctx.lineWidth = 1;
          ctx.stroke();
        }
      }
    }
  }
  
  function animate() {
    ctx.clearRect(0, 0, width, height);
    for (let i = 0; i &lt; particles.length; i++) {
      particles[i].update();
      particles[i].draw();
    }
    drawLines();
    requestAnimationFrame(animate);
  }
  
  window.addEventListener(&#39;resize&#39;, () =&gt; {
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
  });
  
  animate();
})();
</code></pre>
<hr>
<h3 id="예제-c-파도-일렁임-fluid-sine-waves">🌊 예제 C. 파도 일렁임 (Fluid Sine Waves)</h3>
<p>화면 하단에서 여러 갈래의 반투명 파도가 부드럽게 일렁이는 배경 애니메이션입니다.</p>
<pre><code class="language-javascript">(function() {
  const canvas = document.getElementById(&#39;bg-canvas&#39;);
  if (!canvas) return;
  const ctx = canvas.getContext(&#39;2d&#39;);
  
  let width = canvas.width = window.innerWidth;
  let height = canvas.height = window.innerHeight;
  
  let wave1 = {
    y: height * 0.85,
    length: 0.005,
    amplitude: 25,
    frequency: 0.012
  };
  
  let wave2 = {
    y: height * 0.88,
    length: 0.008,
    amplitude: 15,
    frequency: 0.022
  };
  
  let increment = 0;
  
  function animate() {
    ctx.clearRect(0, 0, width, height);
    
    // 첫 번째 뒷 파도 그리기 (반투명 파스텔 청록)
    ctx.beginPath();
    ctx.moveTo(0, height);
    for (let i = 0; i &lt; width; i++) {
      ctx.lineTo(i, wave1.y + Math.sin(i * wave1.length + increment) * wave1.amplitude);
    }
    ctx.lineTo(width, height);
    ctx.fillStyle = &#39;rgba(45, 212, 191, 0.1)&#39;;
    ctx.fill();
    
    // 두 번째 앞 파도 그리기 (반투명 파스텔 하늘)
    ctx.beginPath();
    ctx.moveTo(0, height);
    for (let i = 0; i &lt; width; i++) {
      ctx.lineTo(i, wave2.y + Math.sin(i * wave2.length - increment * 1.5) * wave2.amplitude);
    }
    ctx.lineTo(width, height);
    ctx.fillStyle = &#39;rgba(56, 189, 248, 0.15)&#39;;
    ctx.fill();
    
    // 모바일 환경에 따른 속도 차등 제어
    const speedFactor = (window.bgConfig &amp;&amp; window.bgConfig.mobileThrottleDivisor) ? 0.3 : 1;
    increment += wave1.frequency * speedFactor;
    
    requestAnimationFrame(animate);
  }
  
  window.addEventListener(&#39;resize&#39;, () =&gt; {
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
    wave1.y = height * 0.85;
    wave2.y = height * 0.88;
  });
  
  animate();
})();
</code></pre>
<hr>
<h2 id="6-모바일-개별-배경-설정">📱 6. 모바일 개별 배경 설정</h2>
<p>기기 환경에 맞춰 배경 설정을 차별화하여 성능을 최적화할 수 있습니다.</p>
<ol>
<li><strong>데스크톱 배경</strong>: 데스크톱 환경에서는 <strong>Custom JavaScript</strong>를 선택하여 원하는 Canvas 애니메이션을 구동할 수 있습니다.</li>
<li><strong>모바일 개별 배경 체크</strong>: <strong>[모바일 기기에서 데스크톱과 다른 배경 설정 사용]</strong> 스위치를 활성화합니다.</li>
<li><strong>모바일 배경 경량화</strong>: 독립된 모바일 설정에서 배경을 <strong>Solid(단색)</strong> 또는 **Gradient(그라디언트)**로 선택하여 모바일 리소스 부하를 최소화할 수 있습니다. (선택사항)</li>
<li><strong>이원화 효과</strong>: 배터리 소모와 성능이 중요한 모바일 기기에는 스크립트 실행 부하가 없는 가벼운 배경을 제공하고, PC 환경에는 다채로운 시각 효과를 온전히 표현하도록 배경을 이원화할 수 있습니다.</li>
</ol>
<hr>
<h2 id="7-영역별-배경-조합-및-상속-예시">💡 7. 영역별 배경 조합 및 상속 예시</h2>
<p>헤더, 메인, 푸터에 다양한 배경 타입을 조합하여 독창적인 블로그 디자인을 연출할 수 있습니다.</p>
<ul>
<li><strong>예시 A (독립 연출)</strong><ul>
<li><strong>설정</strong>: 헤더 = Solid(단색) / 메인 = Image(배경 이미지) / 푸터 = Custom JavaScript</li>
<li><strong>결과</strong>: 각 영역이 지정된 개별 설정에 따라 독립적으로 나타납니다.</li>
</ul>
</li>
<li><strong>예시 B (상속 연출)</strong><ul>
<li><strong>설정</strong>: 헤더 = 상위 설정 상속 / 메인 = Custom JavaScript / 푸터 = 상위 설정 상속</li>
<li><strong>결과</strong>: 메인 배경으로 지정한 Custom JavaScript가 헤더와 푸터에 모두 상속되어 화면 전체에 통합된 캔버스 애니메이션이 렌더링됩니다.</li>
</ul>
</li>
</ul>
]]></content:encoded>
            <category>관리자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/admin-design-editor/desktop/img-admin-guide-admin-design-editor-ko-001.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[어드민 핵심 기능 및 듀얼 에디터 개요]]></title>
            <link>https://fixedblog-3lz.pages.dev/admin-guide/admin-core-features</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/admin-guide/admin-core-features</guid>
            <pubDate>Tue, 16 Jun 2026 14:54:27 GMT</pubDate>
            <description><![CDATA[다국어 글 동시 저장, 비주얼과 마크다운 듀얼 에디터 전환 및 기기별 독립 위젯 배치 방법 등 어드민 핵심 기능을 간단히 소개합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="어드민-핵심-기능-및-듀얼-에디터-소개">🎨 어드민 핵심 기능 및 듀얼 에디터 소개</h1>
<p>본 문서는 다국어 동시 발행, 기기별 레이아웃 배치, 실시간 디자인 에디터 등 관리자 페이지(어드민)가 제공하는 핵심 기능들의 기본 구성과 개요를 소개합니다.</p>
<hr>
<h2 id="1-다국어-글-작성-및-듀얼-에디터">📝 1. 다국어 글 작성 및 듀얼 에디터</h2>
<p>글 작성 메뉴에 진입하면 여러 언어 탭이 한 화면에 나란히 배열된 다국어 작성 환경이 제공됩니다.</p>
<h3 id="다국어-글-일괄-작성-및-저장">① 다국어 글 일괄 작성 및 저장</h3>
<ul>
<li><strong>작성 흐름</strong>: 상단 언어 탭(KO, EN, JA 등)을 전환하며 제목, 요약, 슬러그(주소명) 및 본문을 각 언어에 맞게 작성합니다.</li>
<li><strong>일괄 저장</strong>: 하단의 <strong>[모든 탭 동시 저장하기]</strong> 버튼을 통해 작성한 모든 언어의 글들을 데이터베이스에 한 번에 저장할 수 있습니다. <strong>발행</strong> 설정이 되지 않은 언어는 자동으로 <strong>초안</strong>으로 저장되며, 비워둔 언어 탭은 데이터가 생성되지 않고 제외됩니다.</li>
</ul>
<h3 id="듀얼-에디터-지원-비주얼-vs-마크다운">② 듀얼 에디터 지원 (비주얼 vs 마크다운)</h3>
<ul>
<li><strong>비주얼 HTML 에디터 (Visual)</strong>: 일반적인 웹 에디터처럼 서식을 지정하고 미디어를 임베드하며 직관적으로 작성하는 방식입니다. 이미지 파일은 툴바 업로드 버튼으로 본문에 간편하게 삽입할 수 있습니다.</li>
</ul>
<figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-core-features/desktop/img-admin-guide-admin-core-features-ko-002.webp" alt="img-admin-guide-admin-core-features-ko-002" data-align="left" /></figure><ul>
<li><strong>마크다운 에디터 (Markdown)</strong>: 마크다운 문법을 선호하는 사용자를 위해 제공됩니다. 제목, 요약, 카테고리, 태그 등의 정보는 마크다운 헤더 영역에 Front Matter(<code>---</code>) 형식으로 자동 관리됩니다. 저장 시 마크다운 원문과 변환된 HTML 본문이 함께 저장됩니다.</li>
</ul>
<figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-core-features/desktop/img-admin-guide-admin-core-features-ko-003.webp" alt="img-admin-guide-admin-core-features-ko-003" data-align="left" /></figure><h3 id="대표-이미지썸네일-자동-지정">③ 대표 이미지(썸네일) 자동 지정</h3>
<ul>
<li>별도로 대표 이미지를 지정하지 않은 경우, 시스템이 본문 내용을 분석하여 썸네일을 자동으로 설정합니다.<ul>
<li><strong>1순위</strong>: 본문 내용에 삽입된 가장 첫 번째 이미지</li>
<li><strong>2순위</strong>: 본문에 이미지가 없고 유튜브 링크나 iframe 임베드가 포함된 경우, 해당 비디오의 공식 고해상도 썸네일 주소</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-기기별데스크톱모바일-독립-위젯-배치">🧱 2. 기기별(데스크톱/모바일) 독립 위젯 배치</h2>
<p>레이아웃 에디터에서는 마우스 드래그 앤 드롭으로 사이드바와 본문 영역의 위젯 카드 순서를 조정할 수 있으며, 방문자의 기기 유형(데스크톱 또는 모바일)에 따라 독립된 위젯 노출 설정을 구성할 수 있습니다.<br><figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-core-features/desktop/img-admin-guide-admin-core-features-ko-001.webp" alt="img-admin-guide-admin-core-features-ko-001" data-align="left" data-caption="데스크탑/모바일 모드 선택 아이콘" /><figcaption>데스크탑/모바일 모드 선택 아이콘</figcaption></figure></p>
<h3 id="기기별-위젯-노출-조건">⚙️ 기기별 위젯 노출 조건</h3>
<ol>
<li><strong><code>디자인 에디터</code></strong> 메뉴 내 <strong><code>블로그 구조</code></strong> 탭에서 전체 레이아웃의 컬럼 구성 및 너비 비율을 선택합니다.</li>
<li>각 위젯 추가 및 변경 시 노출 기기 조건(Device)을 지정할 수 있습니다.<ul>
<li><strong>Desktop(데스크톱 전용)</strong>: 화면 폭이 넓은 PC 화면에만 위젯을 노출하며, 모바일 접속 시에는 불필요한 리소스 로드를 차단합니다.</li>
<li><strong>Mobile(모바일 전용)</strong>: PC 화면에서는 숨겨지고 스마트폰 및 태블릿 모바일 해상도에서만 표시되도록 제한합니다.</li>
</ul>
</li>
<li><strong>효과</strong>: 모바일 사용자에게 불필요하거나 무거운 위젯의 렌더링과 다운로드를 생략하여, 접속 속도와 스크롤 성능을 최적화합니다.</li>
</ol>
<hr>
<h2 id="3-실시간-디자인-에디터-및-테마-설정">🎨 3. 실시간 디자인 에디터 및 테마 설정</h2>
<p>디자인 에디터에서 변경한 테마 색상 및 스타일 정보는 서버 재빌드나 배포 과정 없이 저장 즉시 방문자 브라우저의 CSS 변수로 반영됩니다.</p>
<ul>
<li><strong>4가지 배경 타입</strong>: 단색(Solid), 그라디언트(Gradient), 배경 이미지(Image), HTML5 Canvas 애니메이션(Canvas) 형식을 선택할 수 있습니다.</li>
<li><strong>글래스모피즘 효과</strong>: 이미지 배경 사용 시 텍스트 가독성을 확보할 수 있도록 불투명도(Opacity)와 흐림(Blur) 수치를 조절하는 유리창 효과 설정을 지원합니다.</li>
<li><strong>인터랙티브 캔버스</strong>: 샌드박스로 격리된 안전한 캔버스 내에서 눈송이, 물결, 별자리 등 움직이는 모션 아트워크를 배경으로 구동할 수 있습니다.</li>
</ul>
<blockquote>
<p>[!TIP]<br>배경 설정 옵션의 종류와 예시 스크립트는 <strong><a href="./admin-design-editor.md">디자인 에디터 설정 소개</a></strong> 문서에서 확인하실 수 있습니다.</p>
</blockquote>
<hr>
<h2 id="4-미디어-저장소storage-지원">💾 4. 미디어 저장소(Storage) 지원</h2>
<p>본문에 사용하는 미디어가 저장될 클라우드 스토리지를 필요에 따라 유연하게 전환하여 운용할 수 있습니다.<br><figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-core-features/desktop/img-admin-guide-admin-core-features-ko-004.webp" alt="img-admin-guide-admin-core-features-ko-004" data-align="left" /></figure></p>
<ol>
<li><strong><code>미디어 라이브러리</code></strong> 메뉴 하단의 <strong><code>저장소 설정</code></strong> 영역으로 이동합니다.</li>
<li>연동할 스토리지 유형을 선택하고 정보를 입력합니다.<ul>
<li><strong>Cloudflare KV</strong>: 에지 노드의 KV 저장소에 에셋을 보관하여 신속하게 서빙합니다.</li>
<li><strong>Cloudflare R2</strong>: 대용량 오브젝트 스토리지를 사용하여 비용 효율적으로 이미지를 관리합니다.</li>
<li><strong>Supabase Storage</strong>: Supabase 스토리지 버킷 경로에 미디어를 업로드하여 안전하게 보관합니다.</li>
<li><strong>ImageKit.io</strong>: 글로벌 이미지 CDN 플랫폼을 연동하여, 업로드한 이미지의 해상도 최적화 및 압축 처리를 활용합니다.</li>
</ul>
</li>
<li><strong>저장소 실시간 전환</strong>: 변경 사항 저장 즉시 업로드 엔진이 해당 저장소로 전환되어 구동됩니다.</li>
</ol>
<blockquote>
<p>[!NOTE]<br><strong>기본 미디어 저장소(Cloudflare KV)의 탐색기 기능 제한</strong><br>Cloudflare KV는 Workers 사용량 및 비용 최적화를 위해 이미지 탐색기 내 목록 조회(조회 API 호출)를 지원하지 않습니다. 반면 Cloudflare R2, Supabase Storage, ImageKit.io 등의 외부 저장소를 설정하여 연결하는 경우에는 업로드된 이미지들의 목록 조회와 미리보기 탐색 기능을 모두 사용할 수 있습니다.</p>
</blockquote>
]]></content:encoded>
            <category>관리자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/admin-core-features/desktop/img-admin-guide-admin-core-features-ko-002.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[어드민 최초 진입 및 환경설정 시작하기]]></title>
            <link>https://fixedblog-3lz.pages.dev/admin-guide/admin-getting-started</link>
            <guid isPermaLink="true">https://fixedblog-3lz.pages.dev/admin-guide/admin-getting-started</guid>
            <pubDate>Tue, 16 Jun 2026 14:37:59 GMT</pubDate>
            <description><![CDATA[관리자가 블로그 시스템을 제어하기 위해 어드민 앱에 안전하게 접근하고, 최초 권한을 획득하며 다국어 UI 사전을 활용하는 방법에 대해 안내합니다.]]></description>
            <content:encoded><![CDATA[<h1 id="어드민-최초-진입-및-환경설정-시작하기">🔑 어드민 최초 진입 및 환경설정 시작하기</h1>
<p>본 문서는 블로그 시스템을 제어하기 위해 관리자가 어드민 페이지에 안전하게 접근하고, 로그인 세션을 유지하며, 화면 상의 각종 문구(다국어 사전)를 손쉽게 조작하는 방법을 안내합니다.</p>
<hr>
<h2 id="1-어드민-로그인-전-ip-보안-통제-정책">🔒 1. 어드민 로그인 전 IP 보안 통제 정책</h2>
<p>이 블로그 엔진은 외부 해킹 시도나 포스팅 등록 권한 탈취를 완벽하게 방지하기 위해 <strong>접속 IP 필터링</strong> 보안 정책을 고수하고 있습니다. 배포 시점에 감지된 관리자의 공인 IP 주소만이 허용 목록(<code>ALLOWED_IP</code>)으로 클라우드 서버에 등록되어 접근을 허용합니다.</p>
<blockquote>
<p>[!IMPORTANT]</p>
<h3 id="안전지대-중심의-운영-원칙-공공장소-사용-금지">🛡️ 안전지대 중심의 운영 원칙 (공공장소 사용 금지)</h3>
<p>본 블로그의 어드민 관리는 반드시 <strong>집이나 신뢰할 수 있는 개인 사무실 등 물리적·네트워크 보안이 확보된 장소</strong>에서만 수행하셔야 합니다.<br>불특정 다수가 이용하고 해킹 위험에 직접 노출된 <strong>PC방, 도서관, 공공 와이파이 환경 등 공공장소에서는 보안 유출 방지를 위해 어드민 접속 자체를 절대 권장하지 않으며 지양해 주시기 바랍니다.</strong></p>
</blockquote>
<p>만약 사용 중인 안전한 장소 내에서 공유기 재부팅이나 인터넷 회선 사정으로 공인 IP 주소가 바뀌어 <code>403 Forbidden</code> 차단 화면을 만나게 된다면, 아래의 절차에 따라 허용 목록을 수동 갱신해 주어야 합니다.</p>
<h3 id="허용-ip-주소-갱신-절차-재배포-필요">⚙️ 허용 IP 주소 갱신 절차 (재배포 필요)</h3>
<ol>
<li>변경된 인터넷 환경에서 터미널을 열고, 블로그 서비스 어드민을 다시 한 번 **재배포(Redeploy)**하는 명령을 실행해야 합니다.</li>
<li><strong>작업 과정 안내</strong>: 데이터베이스 내부를 직접 편집하여 IP를 강제로 기입하는 극단적인 작동 방식보다는 안전하지만, 재배포 명령어가 가동되면서 매번 빌드 프로세스와 파일 업로드 대기 시간이 수 분간 소요되므로 실질적으로 꽤나 번거롭고 불편함이 수반되는 과정입니다.</li>
<li>배포 스크립트가 실행되는 동안 현재 연결된 컴퓨터의 새 공인 IP 주소를 탐지하여 원격 서버의 허용 IP 목록을 최신으로 교체합니다.</li>
<li>배포 처리가 최종 마무리된 후에 어드민 주소로 재접속하면 정상적으로 로그인 화면이 다시 활성화됩니다.</li>
<li>어드민 페이지만 재배포 하시면 됩니다.</li>
</ol>
<hr>
<h2 id="2-관리자-로그인-및-세션-관리">🔑 2. 관리자 로그인 및 세션 관리</h2>
<p>로그인 차단이 정상 해제되면, 최초 구축 시 설정했던 관리자 마스터 비밀번호(<code>ADMIN_PASSWORD</code>)를 입력하여 접속합니다.</p>
<ul>
<li><strong>보안 세션 유지</strong>: 브라우저의 전용 보안 쿠키 설정을 활용하여 30일 동안 어드민 로그인 세션이 안전하게 유지됩니다. </li>
<li><strong>공공 컴퓨터 및 위험 지역 접근 금지</strong>:<br>보안이 입증되지 않은 PC방이나 공용 기기 환경에서는 어드민 로그인 자체를 시도하지 않는 것이 가장 확실한 보안법입니다. 만약 피치 못할 사정으로 공공 환경에서 로그인했다면, 작업을 마친 직후 반드시 어드민 하단의 <strong>[로그아웃]</strong> 단추를 누르고, 추가로 터미널에 <code>npx wrangler logout</code>을 입력하여 로컬 컴퓨터에 기록된 클라우드플레어 관리 계정 정보까지 확실하게 연동 해제(로그아웃)해 주셔야 유출 사고를 방지할 수 있습니다.</li>
</ul>
<blockquote>
<p>[!WARNING]</p>
<h3 id="어드민-접속-주소도메인-노출-최소화-권장">🔒 어드민 접속 주소(도메인) 노출 최소화 권장</h3>
<p>어드민은 허용 IP 목록을 통해 이중으로 안전하게 보호되고 있으나, 악성 공격의 표적이 되는 공격 표면(Attack Surface) 자체를 줄이는 것이 가장 확실한 보안법입니다.</p>
<p>따라서 <code>admin.myblog.com</code>과 같이 알기 쉬운 커스텀 주소를 굳이 연결하지 마시고, Cloudflare Pages에서 기본으로 제공하는 무작위의 서브도메인 주소(예: <code>[프로젝트명].pages.dev</code>)를 그대로 사용하여 어드민의 접속 경로를 비밀스럽게 숨겨두실 것을 강력히 권장합니다.</p>
</blockquote>
<hr>
<h2 id="3-다국어-사전i18n-설정-가이드">🌐 3. 다국어 사전(i18n) 설정 가이드</h2>
<p>포스트 본문 외에 사이트 화면에 고정되어 있는 공통 문구(메뉴 명칭, 댓글 달기 버튼, 로그인 메시지 등)는 소스코드를 한 줄도 수정하지 않고도 사전 기능을 통해 한국어/영어/일본어 등으로 즉시 고쳐 쓸 수 있습니다.<br><figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-getting-started/desktop/img-admin-guide-admin-getting-started-ko-002.webp" alt="img-admin-guide-admin-getting-started-ko-002" data-align="left" /></figure></p>
<p>새로운 지원 언어는 <strong><code>새 언어 추가</code></strong> 기능을 통해 등록할 수 있습니다. 단, 새로 추가한 언어가 화면에 올바르게 표시되려면 기존 사전에 등록된 모든 번역 키(Key)에 대해 해당 언어의 번역본을 빠짐없이 입력해 주어야 합니다.<br><figure data-align="left"><img src="https://sveltekitblog.com/images/posts/admin-getting-started/desktop/img-admin-guide-admin-getting-started-ko-001.webp" alt="img-admin-guide-admin-getting-started-ko-001" data-align="left" /></figure></p>
<ol>
<li>어드민 좌측 메뉴에서 <strong><code>언어 설정</code></strong> 메뉴로 이동합니다.</li>
<li>현재 사이트 전역에 사용되는 각종 번역 사전 키와 저장된 문구 리스트가 하단의 <strong><code>UI 다국어 사전 편집기</code></strong> 구역에 제공됩니다.</li>
<li>번역을 수정하고 싶은 키의 입력란에 원하는 문구(한국어, 영어, 일본어)를 작성하고, 해당 행 오른쪽 끝에 있는 <strong><code>저장</code></strong> 아이콘을 클릭합니다.</li>
<li>저장과 동시에 사이트에 바로 적용되며, 방문자가 블로그에서 언어를 스위칭할 때 실시간으로 변경된 문구로 매끄럽게 번역되어 표시됩니다.</li>
</ol>
]]></content:encoded>
            <category>관리자 가이드</category>
            <enclosure url="https://sveltekitblog.com/images/posts/admin-getting-started/desktop/img-admin-guide-admin-getting-started-ko-002.webp" length="0" type="image/webp"/>
        </item>
    </channel>
</rss>