예고했던 세 가지, 지금까지 온 길
지난 글 마지막에 다음 목표로 세 가지를 적어뒀었다. 관리자 기능 고도화(수정·삭제, About 편집), UI/UX 최적화(반응형 레이아웃), 콘텐츠 작성 기능 확장(이미지 업로드, 수식 렌더링, 리치 텍스트·코드 하이라이팅 에디터).
코드와 커밋 로그를 다시 훑어보니 상당 부분은 이미 되어 있었다. 다만 전부 계획대로 끝난 건 아니다. 이미지 업로드와 리치 텍스트 에디터는 아직이고, 그 자리에는 원래 계획에 없던 .md 파일 불러오기 기능이 대신 들어가 있었다. 무엇을 끝냈고 무엇을 의도적으로 미뤘는지, 그 판단 기준까지 같이 정리해본다.
게시글 수정·삭제 기능
/write 페이지 하나에 작성 폼과 수정 폼을 통합했다. 목록에서 "수정"을 누르면 해당 글 데이터를 폼에 채워 넣고, 저장 시점에는 editingId가 있는지 여부로 생성(POST)과 수정(PUT)을 분기한다.
async function handleSubmit(e: React.FormEvent) {
// ...
if (editingId) {
await updatePost(editingId, { title, summary, content, category, subcategory, tags, status });
router.();
} {
newPost = ({ title, summary, content, category, subcategory, tags, status });
router.();
}
}
백엔드는 PUT /api/posts/:id, DELETE /api/posts/:id를 requireAdmin 미들웨어로 보호해서, 관리자 JWT가 없으면 애초에 요청이 통과되지 않는다. 다만 지금은 삭제에 별도 확인 절차가 없어서, 다음 단계로 확인 모달을 붙일 여지를 남겨뒀다.
About 페이지도 이제 관리자가 편집한다
원래 About 페이지 내용은 하드코딩되어 있었다. 이번에 Category나 Post처럼 백엔드가 데이터를 들고 있고 프론트가 그걸 렌더링하는 구조로 바꿨다. /admin/about에서 자기소개, 스킬, 관심사, 학력·경력, 프로젝트, 연락처를 편집하면 저장 즉시 공개 /about 페이지에 반영된다.
학력이나 프로젝트처럼 배열 형태의 데이터를 "항목 추가 / 삭제" UI로 다루는 부분은 게시글 CRUD와는 결이 다른 관리 화면이라, 폼 하나로 퉁치지 않고 별도 컴포넌트로 분리해서 다뤘다.
마크다운 렌더링 제대로 붙이기 — 수식과 코드 하이라이팅까지
react-markdown에 remark-gfm, remark-math, rehype-katex, rehype-highlight를 조합해서 본문을 실제 마크다운으로 렌더링하도록 바꿨다. 코드 블록 하이라이팅, $수식$ 렌더링,  이미지 임베드까지 이 조합 하나로 커버된다.
raw HTML은 렌더링하지 않고 그대로 문자로 보여주는 쪽을 택했다. 개인 블로그 규모에서 HTML 삽입까지 허용할 이유가 크지 않고, 보안이나 구현 단순함 쪽이 더 남는 선택이라고 판단했다. 렌더링을 붙이고 나니 h1~h6 크기 계층이 살짝 어긋나 있던 것도 눈에 띄어서 같이 손봤다.
비주얼 전체를 갈아엎은 "Devlog Terminal" 리디자인
이번 구간에서 시간을 가장 많이 쓴 건 사실 기능보다 비주얼 쪽이었다. 다크 팔레트, Pretendard와 JetBrains Mono 조합, 사이드바를 터미널 톤으로 바꾸는 작업까지 — 게시글 상세 페이지는 에디터 탭처럼, About 페이지는 README.md 뷰처럼 보이도록 컨셉을 통일했다.
홈 화면의 히트맵도 반응형 통계 스트립으로 바꿨다. 모바일에서 특히 어색했던 부분이라 우선순위를 높여서 정리했다. "기능 추가"와 "톤앤매너 통일"은 성격이 다른 작업이라 이렇게 나눠서 보면 뭘 했는지 더 명확해지는 것 같다.
리치 에디터 대신 .md 파일 불러오기

원래 계획은 Toast UI나 Tiptap 같은 정식 리치 텍스트·코드 하이라이팅 에디터를 붙이는 것이었다. 대신 훨씬 가벼운 방법을 택했다. 평소 쓰던 에디터(VSCode 등)에서 .md 파일로 글을 쓴 다음, 그 파일을 브라우저에서 읽어 본문 textarea에 그대로 채워 넣는 기능이다.
() {
file = e..?.[];
e.. = ;
(!file) ;
(content.() && !()) ;
reader = ();
reader. = ((reader. ?? ));
reader.(file, );
}
핵심은 FileReader로 브라우저 안에서만 파일을 읽는다는 점이다. 서버로 업로드하지 않으니 백엔드나 스토리지는 전혀 건드릴 필요가 없었다. 정식 에디터 라이브러리를 붙이는 비용과, 개인 블로그 규모에서 그게 주는 실익을 비교했을 때 이쪽이 더 합리적이라고 판단했다. 작성 중이던 내용을 실수로 덮어쓰지 않도록 확인창 하나만 얹었다.
아직 남겨둔 것들
이미지 업로드는 여전히 서버에 파일을 올리는 방식이 아니라,  문법으로 외부 URL을 참조하는 방식이다. 진짜 업로드 기능은 다음 과제로 남겨뒀다. 리치 텍스트 에디터도 마찬가지로, 지금은 일반 <textarea> + 마크다운 문법이고 WYSIWYG는 아니다. .md 불러오기가 그 자리를 임시로 대신하고 있는 셈이다.
두 가지 다 "끝났다"고 하기보다는, 이번엔 여기까지 하고 다음 후보로 남겨두는 쪽이 맞는 것 같다.
돌아보며
로드맵에 적어둔 항목을 계획대로 하나씩 다 채우는 것보다, 진행하다가 "지금 규모엔 이게 안 맞는다"고 판단해서 더 가벼운 대안으로 바꾼 결정 쪽이 더 남는 경험이었다. 다음 글에서는 미뤄둔 이미지 업로드부터 다시 다뤄볼 생각이다.