회원 아바타 자산을 전용 경로로 분리해 작가용 미디어 목록과 섞이지 않게 하고, 설정 화면에서 파일 업로드로 바로 반영할 수 있게 했다. Co-authored-by: Cursor <cursoragent@cursor.com>
50 KiB
의사결정 이력
2026-05-11 v0.0.72
회원 썸네일 미디어 분리
회원 썸네일은 운영자(작가) 콘텐츠 제작용 미디어와 목적이 다르므로, 업로드 경로를 /uploads/members/avatars/YYYY/MM로 분리했다. 관리자 미디어 목록에서는 해당 경로를 숨겨, 작가용 미디어 라이브러리와 회원 프로필 자산이 섞이지 않도록 했다.
2026-05-11 v0.0.71
회원 UX를 헤더 중심으로 전환
기존 헤더는 로그인 여부와 무관하게 Anonymous 메뉴를 고정으로 보여 실제 로그인 상태가 사용자에게 전달되지 않았다. 구독 버튼 대신 우측 아바타만 남기고, 로그인 상태에서는 설정/로그아웃 메뉴를 제공해 계정 액션을 한 위치로 정리했다. 비로그인 상태에서는 기존 Sign up/Sign in을 유지한다.
회원 설정/활동 추적과 관리자 멤버 관측
회원 기능이 들어오면서 운영 관점에서 사용자 정보와 활동 추적이 필요해졌다. users에 avatar_url, last_seen_at, last_seen_ip를 추가하고 로그인/세션조회/댓글작성 시 최근 활동을 갱신한다. 관리자는 /admin/members에서 닉네임, 이메일, 최근 접속, IP, 댓글 수를 확인해 운영 판단을 할 수 있다.
닉네임 유니크 정책
사용자 설정에서 닉네임 변경 시 중복 체크가 필요하므로 DB 레벨에서 lower(username) 유니크 인덱스를 도입했다. 기존 중복 데이터로 마이그레이션이 막히지 않도록, 인덱스 생성 전 중복 닉네임은 -2, -3 접미사를 붙여 자동 정리한 뒤 인덱스를 생성한다.
2026-05-11 v0.0.65
통합 검색 모달과 GET /api/search
헤더 검색은 장식이 아니라 Ghost류 UX로 / 단축키·모달·태그·게시물 섹션 구분이 필요했다. INPUT/TEXTAREA 등에 포커스가 있을 때는 브라우저 입력과 충돌하지 않도록 /를 무시한다. 검색은 저장소 searchPublicContent에 모아 LIKE 대신 position(lower(q) in lower(column))로 부분 일치를 구현해 %·_ 이스케이프 이슈를 줄였다. 저자(author) 검색은 현재 도메인 모델에 없어 제외했다.
2026-05-11 v0.0.63
Tailwind 엔트리 단일화
@nuxtjs/tailwindcss 기본 cssPath는 assets/css/tailwind.css인데 저장소에 해당 파일이 없으면 모듈이 패키지 내 tailwind.css를 nuxt.options.css 앞에 끼워 넣는다. 프로젝트는 이미 main.css에 @tailwind와 커스텀 @layer를 두고 있어 두 엔트리가 겹치면 유틸·레이어 순서가 기대와 달라질 수 있다. tailwindcss.cssPath를 main.css로 고정하고, JIT content에 composables·modules·plugins를 포함해 클래스 수집을 보강했다.
2026-05-11 v0.0.62
인증 폼 다크 스타일이 안 보이던 현상
layout/page.vue의 text-ink는 본문에 전달되지만, 폼 컨트롤은 UA 스타일로 color를 상속하지 않는 경우가 많아 다크 배경에서 입력 글자와 currentColor SVG가 사실상 사라질 수 있다. 전역 .auth-form-input으로 텍스트·캐럿·placeholder·WebKit autofill 글자색을 고정하고, 토글 버튼은 SFC scoped 스타일로 동일하게 맞췄다. color-scheme: dark는 네이티브 컨트롤 테마를 맞추기 위해 섹션에 추가했다.
2026-05-11 v0.0.61
인증 폼 비밀번호 토글 아이콘화
보기/숨기기 텍스트는 좁은 모바일에서 시각적 잡음이 되고 다국어·아이콘 일관성도 떨어져, Material 스타일 단일 경로 SVG(눈 열림·가림)를 공통 컴포넌트로 두었다. aria-label은 필드명(field-name)을 받아 회원가입의 확인 필드와 구분한다.
2026-05-11 v0.0.60
홈 Featured 모바일 스크롤·화살표 상태
가로 오버플로 트랙은 기본적으로 스크롤 가능하지만, 카드 전체가 링크일 때 브라우저가 세로 제스처에 가깝게 해석하거나 체인 스크롤이 나는 경우가 있어 touch-pan-x와 overscroll-x-contain으로 가로 우선·부모 스크롤 전파를 줄였다. 화살표는 스크롤 한계에서 의미 없는 클릭을 막기 위해 scrollLeft와 scrollWidth - clientWidth 비교로 disabled를 두고, 레이아웃 변화에 맞추기 위해 ResizeObserver를 함께 썼다.
2026-05-11 v0.0.59
Nuxt #internal/nuxt/paths Node 해석 오류
Nuxt 3.21과 @nuxt/vite-builder는 SSR 엔트리에서 #internal/nuxt/paths를 롤업 외부 모듈로 남기는데, 동일 경로의 paths.mjs 템플릿은 기본적으로 VFS에만 있어 디스크 파일이 없다. Node는 프로젝트 루트 package.json의 imports로만 서브패스를 해석하므로, 템플릿을 디스크에 쓰도록(write: true) 훅하는 로컬 모듈과 루트 imports 매핑을 추가했다. nitro-server 경로만으로 브리지하면 nitropack/runtime 쪽 내부 specifier가 끌려와 단독 해석이 깨지므로, Nuxt가 생성하는 paths.mjs 본문을 그대로 두는 방식을 택했다.
2026-05-11 v0.0.58
중앙 본문과 우측 사이드 가로 넘침
그리드에 lg:px-5 등 패딩이 있는데 열 정의가 287px + 720px + 287px로 합이 max-width와 맞춰져 있으면, 패딩을 제외한 실제 가용 폭보다 열 합이 커지고 main에 width: 720px가 걸려 있으면 중앙 열이 줄어들지 못해 오른쪽으로 삐져 나간다. 중앙 트랙을 minmax(0,1fr)로 두고 본문은 max-width: 720px로만 제한했으며, 열 사이 column-gap으로 우측 사이드와의 간격을 명시했다. 좌측 메뉴를 접었을 때는 gap이 왼쪽에 빈 여백을 만들지 않도록 끄고, 대신 본문에만 우측 패딩을 준다.
2026-05-11 v0.0.57
사이드바 하단 푸터 여백
Thred 참고 레이아웃에서 본문 블록은 pl-4 등으로 호흡이 있는데, 좌측 사이드 푸터만 px-1로 두어 푸터 링크가 시각적으로 왼쪽 벽에 붙어 보였다. 푸터는 스크롤 영역과 동일한 체감 밀도가 나오도록 px-4 이상으로 올리고, 우측 사이드 카피라이트 줄에도 소폭 pr을 맞춰 패널 경계와의 간격을 통일했다.
2026-05-11 v0.0.56
헤더 좁은 데스크톱 폭에서의 밀집 완화
lg 직후(약 10241280px)에서 검색창이 고정 470px이면 3단 그리드와 헤더 액션이 같은 뷰포트 안에서 경쟁해 아이콘과 버튼이 시각적으로 붙는다. 검색창은 flex-1과 구간별 max-w로 축소 가능하게 하고, 헤더·본문 그리드에 lgxl 수평 패딩을 복구해 Thred형 3열을 유지하면서도 호흡을 확보했다.
2026-05-11 v0.0.55
공개 레이아웃 모바일 분기
lg 미만에서는 3열 그리드 대신 세로 흐름으로 본문을 먼저 보여 주고, 오른쪽 사이드는 본문 아래로 내린다. 왼쪽 내비는 화면 폭을 줄였을 때 본문을 밀어내지 않도록 고정 슬라이드 패널로 띄우고, 백드롭 클릭·Escape·헤더 토글로 닫을 수 있게 했다. 데스크톱에서는 기존처럼 그리드 3열과 스티키 사이드바를 유지한다.
2026-05-11 v0.0.54
공개 인증 화면 가독성과 입력 피드백 보정
회원가입/로그인은 현재 백엔드 인증 연동 전 단계이므로, 사용자가 실제 동작 상태를 오해하지 않도록 화면 피드백을 더 명확히 보여주는 것이 우선이라고 판단했다. 회원가입은 모바일 우선 여백과 카드 패널 레이아웃으로 읽기 흐름을 정리하고, 로그인 화면은 오류 메시지와 안내 메시지를 분리해 의미가 섞이지 않게 했다.
로그인 입력에서는 비밀번호 보기/숨기기 토글을 추가해 모바일 환경에서도 오입력을 줄일 수 있게 했다. 회원가입 2단계와 로그인 화면에는 상호 이동 링크를 보강해 사용자 흐름이 한 화면에서 끊기지 않도록 정리했다.
2026-05-11 v0.0.53
게시물 공유 모달 UI
게시물 상세의 제목 오른쪽 공유 버튼은 단순 아이콘만 두지 않고, 모달에서 공유 미리보기 카드와 채널별 링크를 제공하도록 확장했다. 사용자가 외부 공유 전 게시물 정보(썸네일·제목·요약)를 한번 확인할 수 있고, 링크 복사까지 같은 컨텍스트에서 끝낼 수 있어 Thred 참고 UX와 운영 편의성을 함께 맞출 수 있기 때문이다.
헤더 사용자 메뉴 단순화
헤더 우측은 Account 텍스트 링크 대신 아바타 아이콘 버튼으로 전환하고, 비로그인 기준 드롭다운 메뉴에서 Sign up/Sign in만 제공한다. 다크 모드나 메뉴 열기 토글은 이미 헤더와 사이드바에 노출되어 기능이 중복되므로 사용자 메뉴에서는 제거해 정보 밀도를 낮췄다.
회원가입/로그인 공개 화면 초안
회원가입은 /signup 단일 화면에서 3단계(환영, 정보 입력, 이메일 확인) 플로우로 처리한다. 초기 단계에서는 실제 메일 인프라 연결 전이므로 3단계에서 인증 메일 재전송과 인증 완료 액션을 시뮬레이션하고, 인증 완료 후 로그인 화면으로 이동시키는 흐름을 먼저 고정한다. 로그인 화면(/signin)은 같은 다크 톤 레이아웃으로 맞춰 인증 화면군의 시각 일관성을 유지한다.
회원가입 스텝 인디케이터는 단계별 콘텐츠 높이에 따라 위치가 바뀌지 않도록, 화면 높이를 기준으로 한 min-h 레이아웃의 하단 고정 영역에 둔다. 회원가입 1단계 환영 문구는 하드코딩 대신 사이트 설정 API의 title, description 값을 사용해 추후 블로그 이름/인사말 관리 화면과 자연스럽게 연결한다.
2026-05-08 v0.0.52
목록 Featured 아이콘 정렬과 상세 메타 구분자
홈/태그 목록에서 Featured 아이콘을 제목 텍스트 라인에 단순 inline-flex+음수 마진으로 올리면, 특정 폰트 렌더링에서 라인박스 높이가 달라져 카드 높이가 미묘하게 흔들릴 수 있다. 아이콘을 h-4 w-4 고정 박스로 만들고 items-center로 정렬해 제목 줄 높이를 안정화했다. 게시물 상세의 제목 아래 메타 정보는 원본 스킨처럼 / 구분자를 매번 수동으로 넣지 않고, 래퍼에서 after 규칙으로 일관되게 출력하도록 통일했다.
2026-05-08 v0.0.51
사이드바 고정 높이와 발행일 포맷
lg+ 그리드에서 items-start 때문에 사이드바 박스 높이가 콘텐츠만큼만 잡히면 내부 flex-1 스크롤 영역이 늘어나지 않아 푸터가 상단 블록 바로 아래에 붙는다. 데스크톱에서 열 높이를 h-[calc(100vh-57px)](및 동일 max-h)로 고정해 flex 컬럼 안에서 푸터를 열 하단에 두었다. 공개 피드·상세의 발행일은 formatPostDate로 YYYY.MM.DD를 통일하고 <time datetime>에 원본 ISO를 넣어 접근성을 맞춘다.
2026-05-08 v0.0.50
문서 스크롤과 스티키 사이드바
Thred에 가깝게 본문 길이에 따른 세로 스크롤은 main의 overflow-y가 아니라 뷰포트(문서) 스크롤로 통일한다. 스크롤바가 중앙 열에만 생기는 체감을 피하고 브라우저 기본 스크롤 위치와 맞추기 위함이다. 좌·우 사이드는 position:sticky와 max-height: calc(100vh - 헤더)로 고정감을 유지하고, 사이드 내용이 넘칠 때는 스크롤바를 숨긴 채 내부만 스크롤한다.
2026-05-08 v0.0.49
데스크톱 3단 레이아웃 스크롤 영역 분리
Thred 참고 화면처럼 긴 본문이 있을 때도 좌·우 사이드 하단(푸터)이 뷰포트 기준으로 고정되도록, 그리드 행 높이를 calc(100vh - 헤더)로 제한하고 grid-template-rows: minmax(0,1fr)로 자식이 넘치지 않게 한다. 세로 스크롤은 중앙 main에만 두어 사이드바는 내부 스크롤 영역과 고정 푸터로 나눈다.
2026-05-08 v0.0.48
Twitter/X 공식 embed iframe과 북마크·회원가입 마크다운 블록
공개 본문에서 트위터 게시물은 platform.twitter.com/embed/Tweet.html iframe으로 표시한다. oEmbed API나 스크립트 삽입에 비해 구현이 단순하고 SSR·테마(useThemeMode)와 theme 쿼리만 맞추면 라이트/다크 일관성을 유지하기 쉽기 때문이다.
북마크·뉴스레터 CTA는 Ghost/Thred 스킨에서 흔한 카드 패턴이므로 :::bookmark·:::signup 확장 블록으로 저장하고 전용 Vue 컴포넌트로 렌더링한다. 메타데이터 풍부한 북마크는 추후 oEmbed나 서버 페치로 보강할 수 있도록 마크다운 키값 형식을 병행한다.
2026-05-07 v0.0.45
사용자 화면 단일 배경과 사이드바 전환 방식 결정
사용자 화면 라이트 모드는 전체 배경을 #fcfcfc로 통일하고, 영역 구분은 색상 차이가 아니라 보더로만 처리한다. Thred 참고 화면처럼 배경 톤 편차를 줄이면 카드, 사이드바, 본문이 하나의 캔버스 안에서 정돈되어 보이고 시선이 콘텐츠와 타이포에 더 집중되기 때문이다.
왼쪽 사이드바는 열림/닫힘 시 DOM을 제거하지 않고 너비를 애니메이션으로 줄인다. 사이드바가 즉시 사라지면 레이아웃이 튀어 보이므로, 그리드 컬럼과 사이드바 폭을 함께 전환해 스르륵 접히는 느낌을 유지한다.
왼쪽 네비게이션 항목은 기본 상태에서 회색 세로 바를 보이고 hover/focus 시 원형 아이콘으로 전환한다. 정적 상태에서는 구분선을 제공하고 상호작용 시 클릭 가능 영역을 명확하게 드러내기 위해서다.
2026-05-07 v0.0.44
사용자 화면 테마 상태 저장과 샘플 폴더 제외 결정
사용자 화면 라이트/다크 모드는 시스템 테마 자동 감지에만 의존하지 않고 수동 전환 상태를 localStorage.SITE_THEME에 저장한다. 공개 화면 헤더와 사이드바에서 같은 테마 상태를 공유해야 하며, 다음 방문에서도 사용자가 마지막으로 선택한 테마를 유지해야 하기 때문이다.
테마 색상 적용은 CSS 변수와 html[data-theme] 조합으로 처리한다. 기존 prefers-color-scheme는 기본 fallback으로 유지하되, 사용자가 명시적으로 라이트를 고른 경우 시스템이 다크여도 의도한 화면이 유지되도록 우선순위를 분리한다.
Thred 참고용 샘플인 ZCF-v1.0.5와 보관 폴더는 레퍼런스 자료로만 사용하고 Git 추적 대상에서 제외한다. 대용량 정적 자산과 외부 테마 원본이 변경 이력에 섞이면 실제 서비스 코드 변경 검토가 어려워지기 때문이다.
2026-05-07 v0.0.43
대표 이미지 액션과 선택 확정 흐름 결정
대표 이미지가 이미 설정된 상태의 변경과 삭제 액션은 이미지 아래 별도 영역이 아니라 이미지 hover 오버레이로 표시한다. 실제 공개 화면에서 보일 이미지 비율과 편집용 버튼 영역이 섞이면 작성자가 레이아웃을 잘못 인식할 수 있기 때문이다.
대표 이미지 선택 모달에서는 미디어 클릭 즉시 값을 바꾸지 않고 선택 상태만 표시한 뒤, 하단 대표 이미지로 적용 버튼으로 확정한다. 변경 작업은 실수했을 때 되돌리기보다 확정 전 확인이 더 안전한 흐름이기 때문이다.
2026-05-07 v0.0.42
태그 입력과 대표 이미지 선택 흐름 결정
관리자 글 설정의 태그 입력은 단순 텍스트 필드가 아니라 배지형 입력으로 처리한다. 태그 입력 중 Enter가 폼 제출로 전파되면 의도치 않게 게시물이 저장 또는 발행될 수 있으므로, Enter와 쉼표는 태그 추가 동작으로만 사용한다.
Canonical URL과 OG 이미지는 별도 입력 항목에서 제외한다. 현재 운영 흐름에서는 기본 글 주소와 대표 이미지가 자연스러운 기본값이며, 별도 OG 이미지를 관리하면 글 설정 패널이 불필요하게 길어지고 대표 이미지와 공유 이미지가 어긋날 수 있기 때문이다.
대표 이미지는 업로드 탭과 미디어 라이브러리 탭을 함께 제공한다. 작성자는 새 이미지를 바로 올릴 수도 있고, 이미 업로드한 이미지를 재사용할 수도 있어야 하기 때문이다.
2026-05-07 v0.0.41
명령 메뉴 계층과 개발 도구 표시 결정
관리자 블록 에디터의 / 명령 메뉴가 열린 행은 다른 블록 행보다 위 stacking 순서로 올린다. 메뉴가 절대 위치로 열릴 때 아래 블록의 텍스트가 같은 레이어에 남아 있으면 메뉴 배경 위로 겹쳐 보일 수 있기 때문이다.
블록 이동은 drop 이벤트뿐 아니라 dragend에서도 현재 삽입선 위치를 기준으로 확정한다. 브라우저와 입력 요소 조합에 따라 contenteditable 주변에서 drop 이벤트가 안정적으로 들어오지 않을 수 있으므로, 사용자가 본 삽입선과 실제 결과가 어긋나지 않게 하기 위해서다.
개발 서버의 Nuxt DevTools는 현재 관리자 글쓰기 전체 화면 QA를 방해하므로 기본 비활성화한다. 하단 검은 도킹 패널은 애플리케이션 UI가 아니라 개발 도구 영역이지만, 편집 화면 높이와 스크롤 문제를 확인할 때 혼동을 만들 수 있기 때문이다.
2026-05-07 v0.0.40
글쓰기 스크롤과 드래그 드롭 피드백 결정
관리자 글 작성/수정 화면은 글쓰기 라우트에서 관리자 레이아웃 자체를 화면 높이로 고정하고, 실제 세로 스크롤은 에디터 작업 영역 내부에서만 처리한다. 작성 화면 아래로 문서 전체가 밀리면 전역 다크 배경이 노출될 수 있고, Ghost 스타일 전체 화면 편집 모드의 집중감도 깨지기 때문이다.
블록 드래그 이동은 대상 블록 위/아래 절반을 기준으로 삽입 위치를 계산하고 같은 위치에 삽입선을 표시한다. 사용자가 놓는 순간의 결과를 드롭 전에 알 수 있어야 블록형 에디터의 이동 조작이 안전하게 느껴지기 때문이다.
2026-05-07 v0.0.39
블록 에디터 줄바꿈과 핸들 표시 보정 결정
관리자 블록 에디터의 Shift+Enter는 브라우저 기본 동작에 맡기지 않고 에디터가 줄바꿈 문자를 직접 삽입한 뒤 커서를 줄바꿈 뒤로 복구한다. contenteditable의 기본 줄바꿈은 브라우저별로 <div>, <br>, 텍스트 노드 처리가 달라 Vue 상태 동기화와 충돌할 수 있고, 특히 문단 끝에서 커서가 문단 앞으로 이동하는 현상을 만들 수 있기 때문이다.
블록 핸들은 문자 아이콘 대신 AFFiNE 참고 스타일의 세로 막대로 표시한다. 작성 중에는 시각 소음을 줄이고, hover 또는 선택 상태에서는 막대가 블록 높이만큼 늘어나 사용자가 선택, 삭제, 드래그할 범위를 바로 인식하게 하기 위해서다.
2026-05-07 v0.0.38
에디터 문단 모델과 설정 패널 액션 배치 결정
관리자 블록 에디터에서 Shift+Enter는 같은 블록 안의 줄바꿈으로, Enter는 새 문단 블록 생성으로 구분한다. Ghost와 AFFiNE 계열 에디터처럼 한 문단 안의 강제 줄바꿈과 문단 종료가 달라야 블록 선택, 삭제, 이동 범위가 사용자가 인식하는 문단 단위와 맞기 때문이다.
블록 간격은 각 구조형 블록의 위아래 margin을 섞지 않고 다음 블록의 margin-top 기준으로 정리한다. 이렇게 하면 갤러리, 코드, 토글 같은 구조형 블록을 선택했을 때 선택 범위 바깥으로 불필요한 여백이 잡히는 일을 줄이고, 인접 블록 사이 간격도 한 방향에서 관리할 수 있다.
글 수정 화면의 보기와 삭제 액션은 편집 본문 위에 띄우지 않고 우측 게시물 설정 패널 안에 배치한다. 보기 액션은 Post URL과 직접 관련되므로 Post URL 라벨 오른쪽에 두고, 삭제는 파괴적 액션이므로 설정 패널 하단의 독립 버튼으로 분리한다.
2026-05-07 v0.0.37
블록 에디터 입력 안정성과 블록 핸들 범위 결정
관리자 블록 에디터의 한글 조합 입력은 compositionend 직후 DOM 텍스트가 완전히 반영된 다음 슬래시 메뉴 필터와 Enter 처리를 갱신한다. 조합 직후 별도 Enter guard로 입력을 막으면 글자가 확정된 뒤에도 사용자가 Enter를 한 번 더 눌러야 하므로, 조합 중 이벤트만 막고 조합 종료 후에는 즉시 일반 입력 흐름으로 돌린다.
블록 핸들은 1차로 선택, Delete/Backspace 삭제, 드래그 이동까지 제공한다. AFFiNE식 잘라내기, 복사, 서식 툴바, 블록 단위 컨텍스트 메뉴는 작성 경험 전반에 영향을 주는 큰 기능이므로 기본 블록 조작이 안정화된 뒤 별도 단계로 확장한다.
2026-05-07 v0.0.35
관리자 글쓰기 전체 화면 모드 보정 결정
관리자 글 작성/수정 화면에서는 좌측 관리자 네비게이션과 공통 내부 패딩을 숨기고, 글쓰기 폼이 브라우저 높이를 직접 사용하는 전체 화면 편집 모드로 동작하게 한다. Ghost와 WordPress류 편집 화면은 작성 중 관리자 메뉴보다 글 본문과 설정 패널의 관계가 더 중요하므로, 네비게이션이 보이면 작성 영역을 불필요하게 압축하고 시선을 분산시키기 때문이다.
글쓰기 화면의 1차 레이아웃은 상단 헤더 전체 폭이 아니라 에디터 작업 영역과 우측 설정 패널의 좌우 분할로 둔다. 설정 패널이 열리거나 닫힐 때 에디터 작업 영역의 상단 도구막대와 본문 폭이 함께 변해야 사용자가 현재 편집 가능한 폭 변화를 자연스럽게 인식할 수 있다.
2026-05-07 v0.0.32
관리자 글 작성 화면 구조 정리 결정
관리자 글 작성/수정 화면은 별도 페이지 제목 영역을 제거하고, 글쓰기 폼 자체의 상단 도구막대와 본문 중심 레이아웃으로 정리한다. 기존 “새 글 작성” 제목은 현재 작업 맥락을 반복해 화면 높이만 차지했고, 실제 작성 흐름에서는 제목 입력과 본문 시작 위치를 아래로 밀어 Ghost 스타일 편집감과 멀어졌기 때문이다.
대표 이미지는 설정 패널의 부가 항목이 아니라 글 제목 위의 본문 흐름에서 바로 추가하도록 둔다. 게시물 설정은 420px 우측 패널로 분리하고 토글할 수 있게 해, 설정을 열었을 때는 Figma 설정 패널 상태를 따르고 닫았을 때는 집중형 작성 화면을 유지한다.
2026-05-03 v0.0.31
글 미리보기 저장 방식 결정
글 미리보기는 데이터베이스에 임시 초안 레코드를 만들지 않고 브라우저 저장소를 통해 현재 작성 폼 값을 전달한다. 저장 전 내용 확인이 목적이므로 DB에 미리보기용 글이 쌓이거나 슬러그 충돌을 만드는 일을 피하기 위해서다.
미리보기 화면은 /admin/posts/preview 관리자 경로에 두고, 공개 게시물 상세와 같은 ContentRenderer, ProseHeaderCard, ContentMarkdownRenderer 조합으로 본문을 렌더링한다. 이렇게 하면 저장 전에도 공개 화면에 가까운 결과를 빠르게 확인할 수 있다.
2026-05-03 v0.0.30
OG 이미지 저장 방식 결정
게시물 OG 이미지는 대표 이미지와 별도 필드인 og_image로 저장한다. 대표 이미지는 화면 카드와 본문 진입 시각 요소에 쓰이고, OG 이미지는 외부 공유 미리보기 비율과 목적이 다를 수 있기 때문이다.
관리자 입력은 대표 이미지와 같은 미디어 선택/업로드 흐름을 재사용한다. OG 이미지가 비어 있으면 공개 상세 화면에서는 대표 이미지를 fallback으로 사용해 기존 글도 기본 공유 이미지를 가질 수 있게 한다.
2026-05-03 v0.0.29
게시물 SEO 설정 범위 결정
게시물 SEO 설정은 우선 검색 결과에 직접 영향을 주는 SEO 제목, SEO 설명, canonical URL, robots noindex 값만 다룬다. OG 이미지는 대표 이미지 재사용 여부와 별도 이미지 선택 흐름이 더 필요하므로 이번 단계에서는 기본 OG 제목/설명/URL만 공개 상세 화면에 연결하고, 전용 OG 이미지는 다음 작업으로 남긴다.
SEO 제목과 설명이 비어 있으면 기존 글 제목과 요약을 fallback으로 사용한다. 이렇게 하면 모든 글에 값을 강제로 입력하지 않아도 공개 화면의 기본 메타 품질을 유지할 수 있다.
2026-05-03 v0.0.28
예약 발행 저장 방식 결정
예약 발행은 별도 scheduled 상태를 추가하지 않고 기존 published 상태와 미래 published_at 값을 조합해 처리한다. 현재 데이터베이스의 게시물 상태 제약은 published, draft, private만 허용하고 있으므로 상태값을 늘리기보다 공개 API의 조회 조건으로 발행 시각을 확인하는 편이 변경 범위가 작다.
관리자 목록에서는 미래 발행 시각을 가진 published 게시물을 예약 상태로 표시한다. 공개 목록과 상세 API는 published_at이 비어 있거나 현재 시각 이전인 발행 글만 노출한다.
2026-05-02 v0.0.27
미디어 폴더 트리 관리 방식 결정
미디어 폴더는 워드프레스 플러그인형 폴더 UX처럼 왼쪽 트리에서 만들고 선택하지만, 실제 업로드 파일 경로는 이동하지 않는다. 이미 게시물과 페이지에 저장된 이미지 URL이 깨지는 일을 막기 위해 폴더 이동은 media_metadata.category 값을 경로 문자열로 갱신하는 방식으로 처리한다.
빈 폴더도 남길 수 있어야 하므로 media_folders 테이블을 별도로 둔다. 다만 미디어 사용 여부와 공개 렌더링은 계속 URL 기준으로 판단하며, Ctrl/Command 및 Shift 복수 선택과 드래그 이동은 선택된 URL 목록의 메타데이터만 일괄 변경한다.
2026-05-02 v0.0.26
미디어 카테고리 저장 방식 결정
미디어 카테고리는 실제 파일 경로나 URL을 변경하지 않고 media_metadata 테이블에 URL별 메타데이터로 저장한다. 업로드 파일을 폴더별로 이동하면 이미 게시물이나 페이지에 저장된 이미지 URL이 깨질 수 있기 때문이다.
파일명 변경은 사용 중인 미디어에서 차단되어 있지만, 미사용 파일명을 변경할 때는 기존 URL의 메타데이터도 새 URL로 옮긴다. 삭제 시에는 남은 메타데이터가 쌓이지 않도록 함께 정리한다.
2026-05-02 v0.0.25
빈 문단 placeholder 표시와 네비게이션 관리 범위 결정
블록 에디터의 / 안내 문구는 첫 빈 화면이거나 마지막 보조 입력 블록일 때만 표시한다. 사용자가 중간에 의도적으로 만든 빈 문단에도 같은 안내가 반복되면 작성 중인 여백이 오류처럼 보이고, 실제 내용보다 placeholder가 더 강하게 눈에 들어오기 때문이다.
네비게이션 관리는 1차로 공개 왼쪽 사이드바의 상단 메뉴와 하단 링크를 대상으로 한다. 기존 화면에서 이미 해당 영역이 메뉴 역할을 하고 있으므로 새 UI 영역을 만들기보다 하드코딩된 항목을 navigation_items 테이블로 옮겨 관리자에서 라벨, URL, 위치, 순서, 표시 여부를 조정할 수 있게 한다.
2026-05-02 v0.0.24
빈 줄 입력 보존과 사이트 설정 범위 결정
관리자 블록 에디터는 마지막에 클릭 가능한 빈 문단을 유지하지만, 사용자가 Enter로 만든 연속 빈 문단은 자동 삭제하지 않는다. 글 작성 중 여러 줄을 띄워 생각의 구간을 나누는 행동이 자연스럽고, 보조 입력 블록 정리 로직이 사용자의 입력 의도를 지우면 안 되기 때문이다.
사이트 설정은 우선 단일 site_settings 레코드로 관리한다. 개인 블로그 초기 단계에서는 여러 사이트나 다국어 설정보다 사이트 이름, 설명, 기본 URL, 텍스트 로고, 저작권 문구를 안정적으로 저장하고 공개 화면에 반영하는 흐름이 더 중요하다. 이미지 기반 로고와 프로필 이미지는 미디어 사용처 추적과 연결해야 하므로 이후 미디어 설정 확장 단계에서 다룬다.
2026-05-02 v0.0.23
고정 페이지 관리 구조 결정
고정 페이지 작성과 수정은 게시물과 같은 블록형 에디터를 공유하되, 별도 AdminPageForm으로 분리한다. 페이지는 상태, 요약, 태그, 발행일이 없는 정적 콘텐츠이므로 게시물 폼을 그대로 재사용하면 불필요한 필드와 저장 조건이 섞이기 때문이다.
관리자 경로는 내부 리소스 컬렉션 기준으로 /admin/pages/:id를 사용하고, 공개 보기 경로는 기존 고정 페이지 공개 구조인 /pages/:slug를 유지한다. 페이지는 목록과 태그 흐름에 노출되지 않는 독립 콘텐츠로 다루기 위해서다.
2026-05-02 v0.0.22
글쓰기 하단 빈 블록과 저장 피드백 보정
이미지, 갤러리, 임베드 같은 비텍스트 블록이 글의 마지막에 오더라도 작성자가 이어서 글을 쓸 수 있도록 에디터 마지막에는 항상 빈 문단 블록을 유지한다. 이 빈 문단은 작성 편의를 위한 입력 지점이므로 내용이 없으면 저장 마크다운에는 포함하지 않는다.
한글 조합 입력 직후 Enter는 IME 확정 동작으로 들어오는 경우가 있으므로 즉시 새 블록 생성으로 처리하지 않는다. 조합 확정 Enter와 문단 이동 Enter를 분리해 마지막 글자가 다음 블록에 중복 입력되는 문제를 줄이기 위해서다.
저장 버튼을 눌렀을 때 동작 여부가 보이지 않으면 작성자가 같은 동작을 반복할 수 있으므로, 저장/수정/삭제 진행과 결과는 우측 상단 토스트로 표시한다. 새 글 저장 후 수정 화면으로 이동하는 경우에도 성공 토스트를 이어서 표시한다.
2026-05-02 v0.0.21
글 작성 중 자동 저장 범위 결정
글 작성 중 자동 저장은 1차로 브라우저 localStorage에 보존한다. 저장 버튼을 누르기 전까지 서버에 게시물을 생성하지 않으면, 의도하지 않은 초안이 DB에 쌓이거나 슬러그 충돌이 발생하는 일을 피할 수 있기 때문이다.
자동 저장본은 새 글과 기존 글을 서로 다른 키로 분리한다. 작성 화면에 다시 들어왔을 때는 자동으로 덮어쓰지 않고 복원/삭제 선택지를 보여준다. 명시적인 저장이 성공하면 해당 자동 저장본을 삭제해 저장 완료 후 오래된 내용이 다시 나타나지 않도록 한다.
2026-05-02 v0.0.20
콜아웃, 토글, 임베드 블록 저장 방식 결정
콜아웃, 토글, 임베드는 기존 content 마크다운 문자열 안에 :::callout, :::toggle, :::embed fenced block으로 저장한다. 이미지 갤러리와 같은 확장 문법을 사용하면 DB 스키마를 바꾸지 않고도 관리자 작성 화면과 공개 렌더러를 함께 확장할 수 있기 때문이다.
임베드는 1차로 YouTube URL만 iframe으로 렌더링하고, 그 외 URL은 외부 링크로 표시한다. Twitter 등 외부 서비스별 스크립트 임베드는 SSR 안정성과 개인정보/스크립트 로딩 정책을 검토한 뒤 별도 단계에서 확장한다.
2026-05-02 v0.0.19
블록 에디터 조합 입력과 이미지 캡션 표시 보정
관리자 블록 에디터는 한글처럼 조합 과정이 있는 입력 중에는 마크다운 단축 변환과 슬래시 메뉴 상태 확정을 미룬다. 조합 중인 DOM 텍스트를 Vue 상태로 다시 덮어쓰면 마지막 글자가 중복되거나 입력 순서가 어긋날 수 있기 때문이다.
이미지 삽입 시 파일명이나 미디어 제목을 자동으로 alt/caption에 채우지 않는다. 파일명은 작성자가 공개 본문에서 보려는 설명이 아니므로 기본 화면에서는 숨기고, 필요한 경우 이미지 hover 또는 focus 상태에서만 alt 입력을 열어 직접 작성하도록 한다.
2026-05-02 v0.0.18
공개 URL 복수형/단수형 기준 결정
게시물과 태그의 전체 목록은 컬렉션이므로 /posts, /tags 복수형을 사용한다. 개별 게시물과 특정 태그 상세는 하나의 리소스를 가리키므로 /post/:slug, /tag/:slug 단수형을 기준 경로로 정한다.
기존에 사용하던 /posts/:slug, /tags/:slug는 외부 링크나 기존 이동 흐름이 깨지지 않도록 새 단수형 경로로 리다이렉트한다. 관리자 API와 관리자 화면 경로는 내부 관리 리소스 컬렉션이므로 기존 /admin/posts/:id, /admin/tags/:id를 유지한다.
2026-05-02 v0.0.17
대표 이미지와 미디어 화면 밀도 개선 결정
대표 이미지는 URL을 직접 입력하지 않고 미디어 라이브러리에서 선택하거나 새로 업로드하는 흐름으로 통일한다. 게시물 작성자가 파일 URL을 다루지 않아도 되고, 이미 업로드된 이미지를 재사용할 수 있어야 하기 때문이다. 대표 이미지가 설정되면 썸네일과 삭제/변경 액션을 바로 보여준다.
미디어 화면은 수백 개 이상의 파일이 쌓이는 전제를 기준으로 카드형 목록에서 고밀도 썸네일 갤러리로 바꾼다. 파일 경로, 용량, 사용 현황, 파일명 변경, 삭제 같은 상세 정보는 워드프레스처럼 선택한 이미지의 상세 모달에서 확인하고 처리한다.
2026-05-01 v0.0.16
미디어 사용처 표시와 삭제 보호 결정
미디어 라이브러리에서 파일명 변경과 삭제를 제공하면, 해당 이미지가 글 본문이나 대표 이미지에 사용 중인지 먼저 보여줘야 한다. 현재 콘텐츠는 이미지 URL을 게시물/페이지의 content와 featuredImage에 직접 저장하므로, 사용 중인 파일을 변경하거나 삭제하면 공개 화면의 이미지가 깨진다.
따라서 1차 사용처 추적은 게시물과 페이지를 대상으로 본문, 대표 이미지 위치를 표시한다. 사용 중인 미디어의 파일명 변경과 삭제는 차단하고, 미사용 파일만 정리할 수 있도록 한다. 프로필이나 사이트 설정 이미지는 아직 해당 데이터 모델이 없으므로 설정 기능 구현 시 사용처 추적에 추가한다.
2026-05-01 v0.0.15
미디어 라이브러리 1차 범위 결정
글쓰기 화면에서 이미지를 매번 로컬 업로드만 하는 흐름은 장기적으로 불편하므로, 먼저 업로드된 파일을 다시 선택할 수 있는 미디어 선택 창을 붙인다. 관리자 사이드바에는 미디어 메뉴를 추가하고, 업로드된 이미지 목록, 파일명 변경, 삭제를 1차 기능으로 제공한다.
미디어 데이터는 아직 별도 DB 테이블을 만들지 않고 public/uploads 아래 실제 파일 시스템을 기준으로 읽는다. 카테고리 분류와 이미지 사용처 추적은 파일만으로 안정적으로 관리하기 어렵기 때문에 이후 미디어 메타데이터 테이블을 만들 때 함께 확장한다.
2026-05-01 v0.0.14
이미지와 갤러리 블록 구현 범위 결정
관리자 글쓰기의 이미지 기능은 기존 content 필드를 유지하면서 마크다운 확장 문법으로 저장한다. 단일 이미지는 기본 마크다운 이미지 문법에 {width=wide} 같은 너비 옵션만 붙이고, 갤러리는 :::gallery fenced block 안에 여러 이미지 행을 넣는다. 이렇게 하면 DB 구조를 바꾸지 않고 공개 렌더러와 관리자 에디터를 함께 확장할 수 있다.
이번 단계에서는 게시물 작성 중 새 이미지를 업로드하고 글에 삽입하는 흐름을 먼저 구현한다. 워드프레스처럼 이미 업로드된 미디어를 선택하거나 파일명 변경, 개별 삭제, 카테고리 분류를 관리하는 기능은 별도 미디어 라이브러리 메뉴에서 다룬다. 글쓰기 화면이 이후 미디어 라이브러리를 호출할 수 있도록 업로드 API와 저장 URL 기준을 먼저 고정한다.
2026-05-01 v0.0.13
개발 서버 로그 요약 방식 결정
Nuxt 개발 서버의 기본 로그는 프레임워크 상태와 빌드 이벤트를 자세히 보여주지만, 일상적인 로컬 개발에서는 접속 링크만 빠르게 확인하는 편이 더 효율적이다. 따라서 npm run dev는 프로젝트 전용 래퍼 스크립트로 Nuxt dev 서버를 실행하고, 터미널에는 Localhost, Local IP, Admin, Tailwind Viewer 링크를 요약 출력한다.
오류나 경고에 가까운 로그는 계속 터미널에 남긴다. 개발 서버 실행 자체는 Nuxt CLI를 그대로 사용하되 출력만 정리해, 프레임워크 동작 방식은 바꾸지 않고 로컬 사용성만 개선한다.
2026-05-01 v0.0.12
제목과 본문 입력 흐름 보정
관리자 글쓰기 화면에서 제목은 별도 데이터 필드로 유지하되, 키보드 흐름은 본문 에디터와 이어지도록 한다. 제목 입력 중 Enter를 누르면 폼 제출이 아니라 본문 첫 블록으로 포커스를 이동해 Ghost류 작성 화면처럼 하나의 글쓰기 흐름으로 느껴지게 한다.
관리자 에디터 본문 색상은 공개 화면용 post-prose 전역 색상 변수를 그대로 따르지 않고 관리자 화면의 ink 색상으로 고정한다. 시스템 다크모드 변수와 관리자 흰 배경이 섞이면 실제 입력 텍스트가 placeholder보다 흐리거나 읽기 어려워질 수 있기 때문이다.
2026-05-01 v0.0.11
블록 에디터 키보드 흐름 보정
빈 문단에서 Enter를 누를 때도 다음 빈 문단 블록을 생성하도록 유지한다. 작성 중 의도적으로 여백을 두거나 다음 입력 위치로 이동하는 행동이 자연스러운 글쓰기 흐름이기 때문이다. 저장 시에는 기존처럼 비어 있는 블록을 마크다운 문자열에 포함하지 않는다.
슬래시 메뉴는 입력 포커스를 본문 블록에 둔 채 키보드로 선택한다. /제목 3처럼 필터링한 뒤 Enter를 누르면 현재 강조된 항목을 적용하고, 위/아래 방향키로 강조 항목을 이동한다. 이렇게 하면 메뉴 항목으로 실제 DOM 포커스를 옮기지 않아도 Ghost류 에디터처럼 연속 입력 흐름을 유지할 수 있다.
2026-05-01 v0.0.10
블록 에디터 입력 안정화 결정
관리자 블록 에디터는 contenteditable 요소 안의 텍스트를 Vue 템플릿 보간으로 직접 렌더링하지 않고 DOM 참조를 통해 동기화한다. Vue의 렌더 패치와 브라우저의 조합 입력이 동시에 같은 텍스트 노드를 수정하면 / 입력이나 한글 필터 입력이 중복되는 문제가 생기기 때문이다.
슬래시 메뉴는 고정적으로 아래에 열지 않고 활성 블록 위치와 화면 높이를 기준으로 위 또는 아래에 표시한다. 글 하단에서 블록을 추가할 때 메뉴가 화면 밖으로 밀리는 문제를 줄이기 위해서다.
제목은 별도 폼 영역이 아니라 에디터 상단의 큰 제목 입력으로 유지한다. Ghost 작성 화면처럼 제목과 본문이 하나의 흐름으로 보이는 편이 글쓰기 집중도와 결과 화면 예측에 더 가깝기 때문이다.
2026-05-01 v0.0.9
관리자 블록형 글쓰기 방식 결정
관리자 글 작성은 순수 마크다운 textarea가 아니라 Ghost 스타일에 가까운 블록형 에디터를 기준으로 전환한다. 사용자가 / 명령으로 블록을 선택하고, ## 같은 마크다운 단축 입력을 즉시 제목 블록으로 변환해 작성 화면과 결과 화면의 차이를 줄이기 위해서다.
다만 현재 데이터베이스와 API의 content 필드는 그대로 유지한다. 블록 에디터 내부에서는 문단, 제목, 인용, 목록, 코드, 구분선을 블록으로 다루고 저장 시 마크다운 문자열로 직렬화한다. 이렇게 하면 기존 게시물 저장 구조를 깨지 않으면서도 이후 이미지, 임베드, 콜아웃 같은 Ghost 카드형 블록을 단계적으로 확장할 수 있다.
공개 게시물과 고정 페이지 본문도 같은 마크다운 렌더러를 사용하도록 연결한다. 작성 화면과 보는 화면을 완전히 동일하게 만드는 것은 이미지 업로드와 전체 콘텐츠 컴포넌트 구현 이후 다시 보정하되, 이번 단계에서는 제목, 목록, 인용, 코드 등 기본 블록의 시각 차이를 먼저 줄인다.
2026-05-01 v0.0.8
관리자 마크다운 미리보기 방식 결정
관리자 글 편집의 미리보기는 저장 형식을 바꾸지 않고 textarea 입력 위에 작성/미리보기 탭을 추가하는 방식으로 시작한다. 현재 공개 게시물 렌더링이 아직 완전한 마크다운 파서를 사용하지 않기 때문에, 관리자 미리보기는 기본 문법 확인용으로 제한하고 원본 마크다운 문자열을 그대로 저장한다.
편집 편의 기능은 제목, 굵게, 목록, 인용, 코드 블록 삽입 버튼으로 시작한다. 별도 에디터 패키지는 이미지 업로드와 공개 렌더링 방향이 확정된 뒤 필요성을 다시 판단한다.
2026-05-01 v0.0.7
관리자 글 작성/수정 구조 결정
관리자 글 작성과 수정은 AdminPostForm 단일 컴포넌트를 공유한다. 현재 단계에서는 별도 위지윅 편집기를 도입하지 않고 마크다운 textarea 입력을 먼저 저장 가능한 형태로 연결한다. 글 관리의 핵심 흐름인 생성, 수정, 상태 변경을 먼저 검증한 뒤 미리보기, 자동 저장, 이미지 업로드를 분리해 확장하기 위해서다.
발행/초안/비공개 전환은 별도 publish API가 아니라 게시물 수정 API의 status 값으로 처리한다. 초기 관리자에서는 버튼과 API를 늘리기보다 저장 모델을 단순하게 유지하고, 추후 목록에서 빠른 발행 전환이 필요해질 때 별도 액션 API를 추가한다.
관리자 태그 관리 방식 결정
태그 관리는 목록 화면에서 생성/수정 입력을 인라인으로 열지 않고 생성/수정 전용 페이지로 분리한다. 태그에 표시 순서와 색상 코드가 추가되면서 입력 항목이 늘었고, 목록 행 안에서 수정 폼을 열면 테이블 레이아웃이 흔들리기 때문이다.
태그 삭제 시 게시물 자체는 삭제하지 않고 post_tags 연결만 외래 키 규칙으로 정리한다. 태그는 분류 메타데이터이고 게시물 본문 데이터와 생명주기가 다르기 때문이다.
태그의 sort_order는 공개 화면 카테고리 정렬 기준으로 사용하고, color는 태그 옆 색상 바와 이후 태그 배지 배경색에 사용할 수 있도록 #RRGGBB 문자열로 저장한다.
초기 관리자 인증 방식 결정
관리자 기능 1차 구현은 별도 사용자 테이블을 만들지 않고 ADMIN_EMAIL, ADMIN_PASSWORD 환경 변수로 시작한다. 개인 블로그/CMS 초기 단계에서는 운영 계정 수가 하나이고, 데이터 모델을 먼저 늘리기보다 글 관리 흐름을 빠르게 검증하는 편이 유지보수에 유리하다.
로그인 성공 시 /admin 경로에만 적용되는 httpOnly 세션 쿠키를 설정한다. 세션 토큰은 ADMIN_PASSWORD 기반 HMAC 서명으로 검증해 쿠키 위조를 막고, 운영 단계에서 사용자 테이블이나 더 강한 인증 방식이 필요해지는 시점에 확장한다.
로컬 개발 컨테이너 실행 환경 결정
새 개발 환경에서 Docker Desktop 없이 터미널 중심으로 PostgreSQL 개발 DB를 실행하기 위해 Homebrew, Docker CLI, Docker Compose, Colima 조합을 사용한다. 이 방식은 Docker daemon을 Colima가 담당하고, 프로젝트는 기존 docker-compose.yml을 그대로 활용할 수 있어 NAS Docker 배포 구조와 로컬 개발 구조를 크게 벌리지 않는다.
로컬 개발 DB는 .env.development만 사용하고, Docker Compose 실행 시 ENV_FILE=.env.development와 --env-file .env.development를 함께 넘긴다. 이렇게 하면 Git에 포함되지 않는 로컬 비밀번호를 사용하면서도 운영 기본값인 .env.production 기준은 유지할 수 있다.
개발 DB 마이그레이션은 npm run db:migrate:dev로 실행한다. Docker entrypoint는 새 볼륨 생성 시에만 SQL을 자동 실행하므로, 이미 생성된 개발 DB에도 반복 적용할 수 있는 별도 실행 명령을 둔다.
2026-04-29 v0.0.6
환경 변수 파일 보안 기준 정리
.env.example은 Git에 포함되는 공유 템플릿이므로 실제 개인 이메일, 관리자 비밀번호, DB 비밀번호를 기록하지 않는다. 공유 파일에는 placeholder만 두고, 실제 값은 Git에서 제외되는 .env.development와 .env.production에서만 관리한다.
로컬 개발 환경은 127.0.0.1:43119로 개발 DB에 연결하고, NAS Docker 운영 환경은 sori-studio-db:5432로 운영 DB에 연결한다. 개발 DB와 운영 DB는 비밀번호도 분리해 한쪽 값이 노출되더라도 다른 환경으로 전파되지 않게 한다.
이미 원격 저장소에 올라간 비밀번호가 실제 사용 값이었다면 Git 이력에서 지워도 안전하다고 볼 수 없으므로, 해당 값은 폐기하고 새 랜덤 값으로 교체하는 것을 전제로 한다.
2026-04-29 v0.0.5
PostgreSQL 기반 데이터 계층 결정
DB 관리 도구로 CloudBeaver를 고려하고 NAS Docker 배포를 전제로 하기 때문에 초기 데이터베이스는 PostgreSQL로 잡는다. SQLite보다 운영/개발 분리, 외부 관리 도구 연결, 향후 확장에 유리하다.
Nuxt 서버 API는 바로 DB에 강결합하지 않고 server/repositories를 통해 콘텐츠를 조회한다. DATABASE_URL이 설정된 환경에서는 PostgreSQL을 사용하고, 설정되지 않은 환경에서는 샘플 데이터를 사용해 화면과 API 개발을 계속할 수 있게 했다.
Docker Compose에는 앱과 PostgreSQL 서비스를 함께 두되, 실제 운영 비밀번호와 연결 문자열은 .env.production에서 관리한다. DB 외부 포트는 기존 사용 포트와 겹치지 않도록 43119를 사용한다.
2026-04-29 v0.0.4
메뉴 토글 구현 방식 결정
원본 Ghost 테마는 Alpine 스타일의 @click, :class, :aria-expanded 바인딩으로 좌측 메뉴 상태를 제어한다. 이 프로젝트는 Nuxt/Vue 기반이므로 Alpine을 추가하지 않고 Vue 상태와 composable로 같은 기능을 구현한다.
메뉴 상태는 useMenuState에서 공유하고, 브라우저 localStorage의 MENU_STATE에 저장한다. 이렇게 하면 헤더 버튼, 공개 레이아웃, 게시물 레이아웃이 같은 상태를 사용하면서도 별도 프론트엔드 상태 라이브러리나 Alpine 의존성을 추가하지 않아도 된다.
2026-04-29 v0.0.3
공개 화면 테마와 패널 구조 보정
참고 화면 기준으로 공개 화면은 단순한 흰색 페이지가 아니라 헤더 아래 좌측 사이드바, 중앙 본문, 우측 사이드바가 각각 전체 화면 높이를 차지하는 패널 구조로 정했다. 사이드바 콘텐츠가 적어도 패널 자체는 calc(100vh - 57px) 높이를 유지한다.
색상은 초반부터 라이트/다크 모드 기준을 잡기 위해 CSS 변수로 관리한다. 기본 배경, 패널, 라인, 텍스트, 보조 텍스트, 입력, 강조 버튼 색상을 분리해 이후 디자인 조정 시 Tailwind 클래스 전체를 갈아엎지 않도록 했다.
2026-04-29 v0.0.2
Nuxt 통합 백엔드 구조 결정
초기 세팅은 별도 백엔드 앱을 만들지 않고 Nuxt/Nitro의 server/api를 사용한다. 개인 블로그와 CMS를 한 서버에서 배포하면 로컬 개발, NAS 운영, 환경 변수 관리가 단순해진다. DB 연결과 API 라우팅은 Nuxt 서버 영역에서 시작하고, 추후 독립 배포나 워커가 필요해질 때 백엔드 분리를 재검토한다.
Nuxt 3, Tailwind CSS, Zod를 실제 의존성으로 추가하고 공개 화면, 관리자 화면, 콘텐츠 컴포넌트의 초기 골격을 만들었다. 현재 API는 샘플 데이터 기반이며 다음 단계에서 개발 DB로 교체한다.
기본 포트와 사용 중인 포트 충돌을 피하기 위해 로컬 개발 서버는 43117, NAS Docker 외부 포트는 43118을 사용한다. 컨테이너 내부 포트는 Nuxt 기본 실행 흐름에 맞춰 3000으로 유지한다.
2026-04-29 v0.0.1
초기 제품 방향 결정
sori.studio는 개인 블로그를 중심으로 운영하되, 기존 포털 역할은 블로그 내부 링크와 고정 페이지로 흡수한다. 글이 계속 쌓이는 공간이 핵심이므로 공개 화면은 읽기 경험과 콘텐츠 확장성을 우선한다.
관리자 화면은 Ghost의 검증된 글쓰기 흐름과 마크다운 기반 위지윅 방식을 기준으로 삼는다. 개인용 프로젝트라서 관리자 기능은 과도한 추상화보다 단순한 유지보수성을 우선한다.
공개 화면은 Thred 테마의 3단 레이아웃과 콘텐츠 카드 스타일을 참고한다. 헤더, 좌우 사이드바, 중앙 본문 폭을 먼저 고정해 전체 화면 구조를 빠르게 파악할 수 있게 한다.
기술 스택은 Nuxt SSR, JavaScript, JSDoc, Zod, Tailwind CSS를 기본값으로 결정했다. SEO가 필요한 공개 페이지는 서버 렌더링을 우선하고, TypeScript는 초기 복잡도를 낮추기 위해 사용하지 않는다.
Posts와 Pages는 분리한다. Posts는 목록과 태그에 노출되는 블로그 글이고, Pages는 고정 페이지와 포털성 링크 정리에 사용한다.
로컬 개발 환경과 NAS 운영 환경은 서로 다른 데이터베이스를 사용한다. 개인 블로그라도 운영 데이터가 글과 미디어의 원본이 되므로, 로컬 개발 과정에서 운영 DB를 직접 연결하지 않는 것을 기본 원칙으로 정했다.
원격 저장소는 https://git.sori.studio/zenn/sori.studio.git을 사용한다. Git 작성자 정보는 zenn <zenn.message@gmail.com>으로 통일해 이후 커밋 이력을 한 계정 기준으로 유지한다.
note.md는 원본 의도 반영이 끝난 뒤 삭제한다. 이후 프로젝트 기준 문서는 AGENTS.md와 docs/ 아래 문서만 사용한다.