블로그 개발기 7 (포트폴리오와 목차 편)
포트폴리오를 두 벌 만들어 하나를 고르고, 목차 하나를 다섯 번 고친 기록 - 화면은 멀쩡한데 눈에 거슬리는 것들
블로그는 있는데 포트폴리오가 없었다. 글은 30편 가까이 쌓였는데, 정작 "이 사람이 뭘 만들었나"를 한 장으로 보여줄 페이지가 없었다.
"블로그 주소만 주면 읽는 사람이 알아서 찾겠지."
그럴 리가 없다. 그래서 만들기로 했다. 그리고 만들다 보니 목차까지 손대게 됐다. 이틀 동안 커밋은 20개, 73개 파일에 2,536줄이 들어가고 41줄이 빠졌다.
이번 글은 백엔드 얘기가 거의 없다. 대부분 "화면은 멀쩡한데 눈에 거슬리는 것"을 고친 기록이다.
두 벌을 만들고 하나를 골랐다
포트폴리오는 구조를 달리해 두 벌을 만들어 봤다. 하나는 블로그의 다른 페이지처럼 Emotion 컴포넌트를 짝지어 만든 것이고, 다른 하나는 폴더 하나에 CSS 모듈을 두고 데이터 파일이 화면을 끌고 가게 만든 것이다.
| 첫 번째 | 두 번째 | |
|---|---|---|
| 스타일 | Emotion .styled.tsx 짝 | CSS 모듈 하나 |
| 화면 | 프로젝트마다 캐러셀 | 갤러리 + 그룹 필터 + 전체 화면 확대 |
| 이미지 | 25장 | 48장, JSON 에 치수까지 |
| 데이터 | 컴포넌트 안에 섞임 | portfolio.ts 하나 |
며칠 쓰고 나니 답이 나왔다. 두 번째다.
갤러리가 이겼다
결정적인 건 이미지를 다루는 방식이었다. 프로젝트 하나에 화면이 열일곱 장인 게 있다. 캐러셀로 열일곱 장을 넘기는 건 아무도 안 한다. 두 번째 안은 "메인 / 대시보드 / 주문" 같은 그룹으로 걸러 보고, 누르면 전체 화면으로 커진다.
그리고 이미지 치수를 JSON 에 미리 적어둔 게 컸다. next/image 는 폭과 높이를 알아야 자리를 미리 잡는다. 모르면 이미지가 뜨는 순간 아래 내용이 밀린다.
{
"file": "ohana-web-to-lead",
"caption": "Web-to-Lead 화면",
"group": "Web-to-Lead",
"width": 1400,
"height": 643
}
첫 번째 안을 지우고, 두 번째 안의 주소에서 꼬리표를 떼어 /portfolio 로 옮겼다.
주소 하나가 글 하나를 죽일 수 있다
옮기면서 한 줄을 더 고쳤다. 예약 slug 목록이다.
이 블로그의 글 주소는 루트 바로 아래 /[slug] 다. 그런데 /portfolio 는 정적 라우트다. Next.js 는 정적 라우트를 먼저 잡는다. 그러니까 누군가 portfolio 라는 slug 로 글을 쓰면 그 글은 영원히 열리지 않는다. 오류도 안 난다. 그냥 포트폴리오가 뜬다.
const RESERVED_SLUGS = new Set([
"admin", "login", "about", "portfolio", // …
]);
에디터에서 slug 를 검사할 때 이 목록을 본다. 페이지를 하나 만들 때마다 여기에 한 줄이 따라와야 한다는 걸 이번에 규칙으로 적어뒀다.
블로그를 포트폴리오에 넣었다
프로젝트가 다섯이었다. 전부 팀 프로젝트고, 전부 취업 전이다. 그 뒤로 뭘 만들었는지가 없었다.
"지금 이 페이지가 돌아가고 있는 게 프로젝트인데."
그래서 블로그 자체를 첫 번째 항목으로 넣었다. 혼자 설계하고 만들고 운영하는 유일한 것이고, 스택도 지금 쓰는 것이다.
화면을 찍다가 폰트에 속았다
스크린샷은 배포된 사이트를 브라우저 자동화로 찍었다. 그런데 코드블록이 든 화면을 찍었더니 한글이 전부 네모로 나왔다. 본문 한글은 멀쩡한데 코드블록 안의 한글만.
자동화 브라우저에 한글 고정폭 글꼴이 없어서였다. 실제 사용자는 자기 글꼴로 보니까 아무 문제가 없다. 화면이 깨졌다고 결론내리기 전에 환경부터 의심해야 한다는 걸 또 배웠다. 코드블록 없는 화면으로 다시 찍었다.
관리 화면은 로그인이 필요해서 자동화로는 못 찍었다. 이건 직접 찍어서 넣었다. 결과적으로 메인 3장, 포스트 2장, 관리 2장이 들어갔다.
링크에 종류를 붙였다
프로젝트마다 GitHub 링크와 데모 영상 링크가 있었는데, 데이터 필드 이름이 videos 였다. 블로그는 영상이 아니라 배포된 사이트다. videos 에 사이트 주소를 넣는 건 나중의 내가 헷갈릴 게 뻔했다.
links?: { label: string; url: string; kind: "site" | "video" }[];
kind 를 붙이고, 종류에 따라 지구본이나 비디오 아이콘이 앞에 붙게 했다. 아이콘은 인라인 SVG 에 currentColor 를 써서 글자와 같이 호버 색을 따라간다. 이미지 아이콘이었으면 다크 모드에서 반전 필터를 걸어야 했을 것이다.
같은 모양은 같은 뜻이어야 한다
Skills 절에는 "적용 프로젝트" 링크가 있다. 이걸 태그 칩 모양으로 만들까 잠깐 고민했다. 그런데 바로 아래 프로젝트 카드의 "사용한 기술"이 이미 칩이다. 같은 모양이 한 페이지에서 "기술"과 "프로젝트로 가는 링크" 두 뜻을 갖게 된다.
그래서 칩은 버리고, 프로젝트 카드의 GitHub 링크가 쓰는 것과 같은 움직임으로 맞췄다. 평소엔 밑줄이 없고, 호버하면 왼쪽에서 오른쪽으로 선이 그어진다. 한 페이지 안에서 링크 언어는 하나여야 한다.
갤러리의 그룹 선택도 브라우저 기본 <select> 그대로였다. 옆의 이전·다음 버튼이 알약 모양이라 그것만 튀었다. appearance: none 으로 벗기고 같은 알약으로 맞췄다. 화살표는 이미지 대신 테두리 두 변을 45도 돌려 그렸다. 그래야 currentColor 로 테마와 호버 색을 따라간다.
.galleryHeading label::after {
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: translateY(-70%) rotate(45deg);
}
목차
포트폴리오가 길어지자 어디쯤 읽고 있는지 알 길이 없었다. 목차를 붙이기로 했다. 붙이는 김에 블로그 본문에도.
하나로 만들었다
본문 목차는 렌더된 HTML 에서 h1~h6 을 읽어 만든다. 제목 레벨을 스택에 쌓으면서 상위·하위를 가른다.
while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
const parent = stack[stack.length - 1]?.item;
if (parent) (parent.children ??= []).push(item);
else roots.push(item);
포트폴리오는 절이 정해져 있으니 데이터로 준다. 소개·Skills·프로젝트(하위에 프로젝트 여섯)·연락처. 둘 다 같은 TableOfContents 컴포넌트를 쓴다. 넓은 화면에서는 본문 옆에 떠 있고, 좁으면 본문 아래로 내려간다.
누르면 중간을 다 지나갔다
처음 붙였을 때 두 가지가 이상했다.
하나. 목차를 누르면 부드럽게 스크롤되는데, 지나가는 절마다 목차 강조가 차례로 깜빡였다. 스크롤 이벤트마다 위치를 다시 재니까 당연한 결과였다. 누른 항목을 기억해두고, 스크롤이 멎을 때까지는 그걸 유지하게 했다. 대신 휠이나 키보드로 사용자가 직접 움직이면 바로 다시 추적한다.
둘. 마지막 절이 끝내 강조되지 않았다. 강조 기준선이 화면 위에서 200px 쯤인데, 마지막 절은 페이지가 더 못 내려가서 그 선에 닿지 못한다. 절의 시작점과 "스크롤이 갈 수 있는 최대"를 비교해 작은 쪽을 쓰게 했다.
const activationPoint = Math.min(sectionTop - threshold, maxScroll);
배경을 뺐다
동작은 됐다. 그런데 볼수록 거슬렸다. 현재 항목에 옅은 배경을 깔았는데, 목차 폭이 200px 남짓이라 제목이 두 줄로 접히는 게 잦다. 그때마다 울퉁불퉁한 덩어리가 생겼다.
배경은 "버튼"처럼 읽힌다. 목차 강조는 버튼이 아니라 지금 어디를 읽고 있나라는 위치 표시다. 문서 사이트들이 왼쪽에 세로선을 긋고 그 위에 마커를 붙이는 이유가 그거다. 그렇게 바꿨다.
굵게 하면 줄이 접힌다
레일로 바꾸면서 현재 항목을 굵게도 했다. 그랬더니 스크롤할 때마다 목차 줄이 접혔다 펴졌다 했다.
굵어지면 글자 폭이 는다. 폭 끝에 걸린 제목이 두 줄이 된다. 12개 항목을 하나씩 강조해 보며 높이를 쟀더니 접히는 게 있었고, 굵기 변경을 빼니 0개가 됐다. 강조는 색과 마커로만 한다.
상위와 하위가 안 갈렸다
그 다음은 계층이었다. 상위도 회색, 하위도 회색, 차이는 들여쓰기 12px 와 글자 1px. 구분이 안 됐다.
신호를 네 개로 늘렸다.
| 신호 | 상위 | 하위 |
|---|---|---|
| 색 | 본문색 | 보조색 |
| 굵기 | 700 | 400 |
| 간격 | 항목 사이 6px | 없음 |
| 레일 | 바깥 | 안쪽에 하나 더 |
하위 묶음을 안쪽 레일로 한 번 더 감싸니 "이 절 안의 것들"이 눈에 들어왔다.
선 길이가 어긋났다
마지막 하나. 안쪽 레일은 하위 항목 높이의 합인데, 마커는 위아래 4px 씩 안쪽으로 들어가 있었다. 하위가 하나뿐인 묶음에서는 레일보다 마커가 8px 짧아서, 파란 선이 회색 선 위에 덜 그어진 것처럼 보였다.
마커를 항목 높이에 꽉 채웠다. 재보니 항목 41.375px, 레일 41.375px. 이제 하위가 하나면 레일이 통째로 파란색이 되고, 여럿이면 그 항목 구간만 정확히 칠해진다.
"1px 짜리 선 두 개 길이 맞추는 데 커밋 하나."
그래도 눈에 거슬리는 건 거슬리는 거다.
결과
| 전 | 후 | |
|---|---|---|
| 포트폴리오 | 두 벌, 주소에 꼬리표 | 한 벌, /portfolio |
| 프로젝트 | 5개, 전부 취업 전 | 6개, 블로그 포함 |
| 손으로 적은 숫자 | 연도·개수 셋 | 없음 |
| 링크 | videos 에 뭐든 | links + kind + 아이콘 |
| 목차 | 없음 | 본문·포트폴리오 공용, 계층형 |
| 목차 강조 | 배경 박스, 굵게 | 레일 마커, 색만 |
목차 하나를 다섯 번 고쳤다. 커밋 메시지를 나란히 놓으면 "강조를 배경 대신 레일로 → 굵기를 빼서 줄이 안 접히게 → 상·하위를 나눠 → 마커 길이를 레일에 맞춰" 순서다. 한 번에 됐으면 좋았겠지만, 하나를 고치면 다음 게 보였다.
느낀점
- 두 벌 만들어 보는 게 한 벌 고민하는 것보다 빨랐다. 머리로 비교하면 답이 안 나오던 게, 둘 다 띄워놓고 며칠 쓰니 바로 갈렸다.
- 화면이 깨졌다고 판단하기 전에 환경을 본다. 코드블록의 한글 네모는 내 사이트가 아니라 찍는 브라우저의 문제였다.
- 강조는 크기를 바꾸지 않는 방식으로. 굵기·크기가 바뀌면 레이아웃이 따라 움직인다. 색과 마커는 자리를 차지하지 않는다.
읽어주셔서 감사하다.
댓글