React, TypeScript, Vite로 만든 개인 기술 블로그입니다.
문제 해결 과정, 백엔드 아키텍처, 운영 경험, 프론트엔드 개선 기록을 Markdown 기반으로 발행합니다.
Devy Archive는 단순한 SPA가 아니라, GitHub Pages에서 안정적으로 동작하도록 hydrated SSG를 얹은 개인 기술 아카이브입니다. 글은 content/posts/*.md로 관리하고, 빌드 시 RSS, sitemap, route별 HTML, SEO metadata를 함께 생성합니다.
|
Markdown publishing frontmatter, GFM, raw HTML, 태그, 시리즈, 예약 발행, draft 지원 |
Hydrated SSG SSR HTML을 빌드 산출물에 주입하고 클라이언트에서 안전하게 hydration |
|
Technical writing UX Shiki dual-theme code highlighting, Mermaid diagram, TOC, 읽기 시간 |
Discovery 전체 검색, 고급 검색, 태그별 목록, 시리즈별 목록, Cmd+K command menu |
|
Personalization light/dark/system theme, color theme, 한국어/English language toggle |
Operations Google Analytics page views, Giscus comments, RSS, sitemap, OG metadata |
flowchart LR
Posts["content/posts/*.md"] --> Glob["import.meta.glob"]
Glob --> Parser["src/lib/posts.ts"]
Parser --> Pages["React Router pages"]
Pages --> Client["CSR hydration"]
Pages --> Server["src/entry-server.tsx"]
Server --> Prerender["scripts/prerender.mjs"]
Prerender --> Dist["dist HTML"]
Parser --> Feed["RSS / Sitemap"]
Feed --> Dist
빌드 흐름은 tsc -> Vite client build -> Vite SSR build -> hydrated prerender 순서로 실행됩니다. 글 상세, 프로젝트 상세, 404 fallback까지 정적 HTML을 생성해 GitHub Pages의 SPA fallback에서도 hydration mismatch를 줄입니다.
| Path | Page | Description |
|---|---|---|
/ |
Home | 최신글, 인기글, 블로그 통계 |
/posts |
Posts | 전체 글 목록, 검색, 정렬, 태그/연도 필터 |
/posts/:slug |
Post | Markdown 글 상세, TOC, 댓글 |
/tags |
Tags | 태그별 주제 탐색, 관련 태그, 최근 글 |
/series |
Series | 시리즈별 글 탐색 |
/analytics |
Analytics | 방문자 및 조회수 대시보드 |
/about |
About | 소개, 경력, 프로젝트 요약 |
/about/projects/:slug |
Project Detail | 프로젝트 상세 |
pnpm install --frozen-lockfile
pnpm devpackageManager에 pnpm 11.17.0을 고정합니다. 패키지 파일은 머신 전역 pnpm store에서 중복 없이 재사용하며, 프로젝트의 node_modules에는 실행에 필요한 링크만 생성되고 Git에서는 제외됩니다.
개발 서버는 기본적으로 http://localhost:5173에서 실행됩니다.
| Command | Description |
|---|---|
pnpm dev |
Vite 개발 서버 실행 |
pnpm build |
타입 체크, client/SSR 빌드, hydrated SSG 생성 |
pnpm preview |
dist 결과 미리보기 |
pnpm lint |
ESLint 검사 |
pnpm type-check |
TypeScript 타입 검사 |
content/posts/에 Markdown 파일을 추가하면 빌드 시 자동으로 로드됩니다.
---
title: "제목"
date: "2025-01-01"
updated: "2025-02-01" # 최종 수정일
description: "설명"
tags: ["react", "typescript"]
series: "시리즈명" # optional
seriesOrder: 1 # optional
draft: true # optional, production에서 숨김
publishDate: "2025-12-01" # optional, 예약 발행
---draft: true 또는 미래의 publishDate가 있는 글은 production 빌드에서 자동으로 제외됩니다.
date는 최초 게시일, updated는 최종 수정일이며 둘 다 필수입니다. 처음 게시할 때는 같은 값을 기록하고, 이후 본문·구조화 데이터·주요 링크처럼 검색 결과에 영향을 주는 내용을 실제로 수정했을 때만 updated를 변경합니다. 게시일보다 빠르거나 미래인 수정일은 빌드에서 거부됩니다.
.
├── content/posts/ # Markdown posts
├── public/ # favicon, OG image, robots.txt, CNAME
├── scripts/
│ └── prerender.mjs # SSR 결과를 dist HTML에 주입
├── src/
│ ├── components/ # UI, markdown, comments, charts
│ ├── data/ # resume/project data
│ ├── hooks/ # theme, meta, page views
│ ├── i18n/ # ko/en translations
│ ├── layouts/ # root layout
│ ├── lib/ # posts, analytics, shiki, utils
│ ├── pages/ # route pages
│ ├── routes.tsx # client routes and lazy loading
│ ├── routes.server.tsx # server prerender routes
│ └── entry-server.tsx # React SSR entry
└── vite.config.ts # Vite, manual chunks, RSS, sitemap
| Area | Stack |
|---|---|
| App | React 19, TypeScript 5.8, Vite 8 |
| Routing | React Router v7 |
| Styling | Tailwind CSS v4, shadcn/ui, Radix UI |
| Content | react-markdown, remark-gfm, rehype-raw, rehype-slug |
| Code & Diagram | Shiki, Mermaid.js |
| Data Viz | Recharts |
| Community | Giscus |
| Analytics | Google Analytics, Google Apps Script API |
| Deploy | GitHub Pages, custom domain dev.devy.dev |
GitHub Pages로 배포하며, 커스텀 도메인은 public/CNAME의 dev.devy.dev를 사용합니다. 빌드 시 rss.xml, sitemap.xml, route별 prerendered HTML이 함께 생성됩니다.
-
데스크톱의 접기·검색은 사이드바에 모으고, 모바일에만 48px 상단 바를 표시합니다. 검색 창은 하나로 공유하며
⌘K와Ctrl+K를 지원합니다. -
본문 최대 폭은 800px이며, 1280px 이상에서는 옆 목차를 사용합니다. 작은 화면에서는 접는 목차를 제공합니다.
-
절 링크와 목차 선택은 URL hash에 반영되며, 뒤로가기 시 읽던 위치를 복원합니다.
-
공개 이미지 원본은 유지하고, Vite 이미지 플러그인이 640/1280/1600px WebP를 생성합니다. 본문 이미지는 크기를 예약하고 지연 로딩합니다. 개발 서버에서도 같은 압축본을 제공합니다.
-
검색 필터는 hydration이 끝난 뒤 URL에서 읽습니다. 미리보기의 없는 경로는 GitHub Pages처럼
404.html을 HTTP 404로 응답합니다. -
조회수는 메모리와 sessionStorage에 15분 동안 캐시합니다. 저장소 차단, 손상된 캐시, API 오류에 대비하며 갱신 실패 시 마지막 정상 데이터를 유지합니다.
-
일별 조회수 비교는 KST 달력 날짜를 사용합니다.
apps-script/Code.js는 60일 조회 및 명시적 실패 응답으로 변경했습니다. 외부 Apps Script 웹 앱에는 이 소스를 별도로 반영해야 합니다. -
예약 글은 UTC 날짜를 기준으로 필터링하고, 매일 KST 09:10의 배포 워크플로에서 다시 빌드합니다.
pnpm type-check
pnpm lint
pnpm build
node --test scripts/*.test.mjs
pnpm check:internal-links
pnpm exec playwright install chromium
pnpm test:browser
pnpm preview --host 127.0.0.1 --port 4173브라우저 검사는 검색 URL, 404 hydration, 절 링크, 읽던 위치 복원, 모바일 목차, 이미지 크기 예약, 키보드 다이어그램 확대와 저장소 차단을 확인합니다. CI에서도 실행하며, ESLint 경고와 High 이상 의존성 취약점을 차단합니다. shadcn의 정적 variant 및 sidebar hook export는 Fast Refresh 검사 예외로 유지합니다.