BinaryHub 개발

BinaryHub 개발기 ⑪ 비직관적인 사이트 개편 — wayfinding 의 작은 단서들

5
BinaryHub 개발기 ⑪ 비직관적인 사이트 개편 — wayfinding 의 작은 단서들

BinaryHub 개발기. 그럴듯해 보이지만 어디가 어디인지 모르는 사이트를, 큰 갈아엎기 없이 단서만 채워 고치기.

어느 날 운영자가 보낸 한 문장

"뭔가 그럴듯해 보이긴 하지만 지금 형태가 비직관적이고 계속 항목들이 변해서 사이트 구조 파악이 사용자 입장에서 쉽지 않아."

이 문장이 정확했다. 글이 50편 넘게 쌓이고, 카테고리 8 개, 부원 8 명, 위키·관리 라우트까지 추가되면서 사이트가 기능적으론 충분히 굴러가고 시각적으로 깔끔한데, 정작 사용자 입장에서 "여기가 어디지?" 가 답이 안 나오는 상태였다.

큰 refactor 가 아니라, 누락된 단서 만 채우는 작업으로 시작했다.

네 가지 질문으로 정리

NN/g 와 Steve Krug 의 "Don't Make Me Think" 가 반복해서 강조하는 게 결국 네 가지 질문이다.

사용자 질문 현 상태 결과
여기가 어디인가? 시각 breadcrumb 없음 (JSON-LD 만), 글 페이지의 "← 목록으로" 한 단계 뿐 위치 모름
어디로 갈 수 있나? drawer 사이드바 좋으나 active state X. 글 추가될 때마다 list 가 변함 다음 step 추측
무엇이 stable 한가? Header/Footer 만 일관. H1 크기 (5xl/3xl/2xl), 컨테이너 폭, 카드 패턴 (3 종) 페이지마다 다름 "또 바뀌었네"
사이트 전체 구조? 사람용 site-map 없음. 포털 5 카드만 overview 부재

한 줄로 요약하면 — landmark 가 부족했다. 사용자가 어디 있는지·어디로 갈 수 있는지·무엇이 바뀌지 않는지를 알 수 있는 시각 단서.

토큰화부터

가장 먼저 한 일은 클래스 이름을 부여 한 것. 페이지마다 다른 max-w-2xl, max-w-3xl, text-3xl, text-2xl 이 흩어져 있어서 왜 다른지 가 코드에서도 안 보였다.

:root {
  --container-narrow: 42rem;
  --container-default: 48rem;
  --container-wide: 64rem;
}
.page-shell { /* 목록·인덱스용 */
  max-width: var(--container-default);
  padding: 2.5rem 1.5rem;
  margin: 0 auto;
}
.prose-shell { /* 글 본문용 */
  max-width: var(--container-narrow);
  /* xl 에서 TOC 옆자리 확보 */
}
@media (min-width: 1280px) {
  .prose-shell { max-width: var(--container-wide); }
}
.page-h1 {
  font-size: 1.875rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}
@media (min-width: 640px) {
  .page-h1 { font-size: 2.25rem; }
}

대부분 페이지는 기존 폭과 동일한 토큰 으로 바뀐 거라 시각 변화는 없다. 다만 코드에서 max-w-3xlpage-shell 로 바뀌면서, 어떤 의미로 이 값을 골랐는지 가 비로소 명시됐다. club/[member]/page.tsx 의 H1 만 유일하게 text-2xl 이라 통일에 의한 실제 변화 가 있었다.

토큰화는 SEO·접근성 모두 직접 효과는 없지만, 앞으로 새 페이지 만들 때 어디서 시작할지 가 분명해진다. 더 이상 한 줄씩 외워서 박지 않아도 된다.

// components/Breadcrumb.tsx
<nav aria-label="현재 위치">
  <ol className="flex items-center gap-1 flex-wrap">
    {items.map((item, idx) => {
      const isLast = idx === items.length - 1;
      return (
        <li key={idx}>
          {idx > 0 && <ChevronRight aria-hidden />}
          {isLast ? (
            <span aria-current="page">{item.label}</span>
          ) : (
            <Link href={item.href}>{item.label}</Link>
          )}
        </li>
      );
    })}
  </ol>
</nav>

NN/g 가 모든 IA 개선 의 첫 번째로 권하는 패턴. 이미 글 페이지엔 BreadcrumbList JSON-LD 가 있었지만 그건 Google 만 본다. 사용자에게도 보여주려면 시각 breadcrumb 가 필요했다.

9 개 페이지에 같은 컴포넌트로 적용 — 각자 항목만 다르게.

  • /blog/category/tetris → BinaryHub / 블로그 / 테트리스 위키
  • /blog/posts/<slug> → BinaryHub / 블로그 / 카테고리 / 제목
  • /club/<m>/posts/<s> → BinaryHub / 동아리 / 멤버 / 제목

마지막 항목은 link 없이 aria-current="page" 로 표시 — 스크린리더가 "여기가 현재 페이지" 를 읽어 준다. JSON-LD 의 BreadcrumbList같은 항목 으로 유지해서 SEO·UI 가 한 줄기로 흐른다.

글 페이지의 옛 "← 목록으로" 한 줄짜리 백 링크는 제거. breadcrumb 가 더 풍부한 정보를 주니까 중복 신호를 정리한 셈.

Drawer 의 현재 페이지 강조

이미 drawer 사이드바 안에 카테고리·태그·최근 글 list 가 있었다. 다만 내가 지금 보고 있는 카테고리 가 어디인지 안 보였다.

// components/ActiveLink.tsx — Server component 안에서도 쓸 수 있는 작은 client island
"use client";
export default function ActiveLink({ href, exact = false, ... }) {
  const pathname = usePathname() || "";
  const isActive = exact ? pathname === href : pathname.startsWith(href);
  return (
    <Link href={href} aria-current={isActive ? "page" : undefined}
          className={`${className} ${isActive ? activeClassName : ""}`}>
      {children}
    </Link>
  );
}

SidebarContent 를 통째 client 로 만들면 getAllPosts() 같은 fs 함수가 깨진다. 그래서 각 link 만 client island 로 분리. 데이터는 server 가 계속 모으고, 현재 페이지인지 만 클라에서 판단.

활성 항목에 왼쪽 2 px accent bar 를 둔 게 작은 디테일 — TOC 의 active 글 표시 방식과 동일한 패턴이라 사이트 전체가 같은 위치 강조 어휘 를 쓰는 인상이 된다.

사람용 site-map

XML sitemap 은 Google 이 보는 거고, 사람이 사이트 전체 구조를 한 번에 보는 페이지 가 따로 없었다. /site-map 한 페이지 만들었다.

다섯 섹션:

  1. 우산 사이트 다섯 개 (블로그·동아리·북곽툴즈·StudyAce·무쓸모)
  2. BBlog 카테고리 8 개 + 글 수 + 카테고리 description
  3. 동아리 부원 8 명 + 각자 글 수
  4. 자주 쓰이는 태그
  5. 메타 페이지 (최근 변경·RSS·sitemap.xml·llms.txt·소개·약관)

JSON-LD SiteNavigationElement 도 같이 박았다. Footer 우측에 "사이트 맵" 한 줄 추가 — 어디서나 한 클릭으로 도달.

접근성 작은 것들

큰 일은 아니지만 wayfinding 관점에서 키보드 사용자에게도 같은 단서를 줘야 했다.

:focus-visible {
  outline: 2px solid var(--accent-focus);
  outline-offset: 2px;
}
.skip-link {
  position: absolute; top: 0; left: 0;
  transform: translateY(-110%);
  transition: transform 0.15s;
}
.skip-link:focus { transform: translateY(0); }

Tab 키 1 회 → 화면 좌상단에 "본문으로 건너뛰기" 가 슥 내려온다. 각 <main>id="main" tabIndex={-1} 을 부여해 그게 anchor 가 된다. 한 줄짜리 변경이 시각·키보드 양쪽 사용자에게 똑같은 현재 위치 단서를 준다.

마지막 디테일 — 링크가 텍스트랑 구별되는지

"하이퍼링크 넣어놓은 게 그냥 텍스트랑 구별이 잘 안 가는데 aesthetics 를 해치지 않으면서 표시할 방법 없을까"

이게 가장 마지막에 받은 피드백이었다. 본문의 <a> 가 이미 accent 색이긴 했지만, 정적 상태에서 밑줄이 없어서 색 contrast 만으로 link 인지 알아채야 했다. 색맹·약한 contrast 사용자에겐 보이지 않을 수 있다.

Apple / Stripe / Vercel 의 표준 패턴 — 항상 옅은 밑줄, hover 시 진해짐 — 으로 교체했다.

.prose-post a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 35%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: text-decoration-color 0.15s, text-underline-offset 0.15s;
}
.prose-post a:hover {
  text-decoration-color: var(--accent);
  text-underline-offset: 0.3em;
}
.prose-post a[href^="http"]:not([href*="binaryhub.club"])::after {
  content: "\00a0↗";
  font-size: 0.8em;
  opacity: 0.6;
}
  • 평소 — accent 색 + 35 % 투명도 밑줄 + offset 0.2em
  • Hover — 밑줄 진해지고 offset 0.3em (살짝 떠오름)
  • 외부 링크 — 자동으로 작은 추가 (binaryhub.club 외 도메인만)
  • Heading 안의 anchor 는 본문 link 스타일 제외 — :not() 같은 셀렉터 없이 .prose-post h2 a, .prose-post h3 a 에 별도 override

색만으로 약했던 시그널이 항상 옅은 밑줄 이 더해지면서 잡힌다. 그러면서도 Apple-style 톤은 깨지지 않는다.

안 한 것들

같은 라운드에 안 한 것도 명시해둔다. 과한 변경 을 막기 위해서.

  • 다크 모드 — CSS variable 구조는 이미 준비됐지만 shiki 코드 블록까지 신경 써야 해서 별도 라운드
  • 전역 Cmd+K command palette — cmdk 라이브러리 + 검색 인덱스 빌드. 가치 크지만 시간 듬
  • 모바일 TOC — 긴 글에서 mid-screen 사용자에게 누락된 기능. 추후
  • 카테고리 컬러 시스템 — 부원 색과 같이 카테고리에도 색 부여. 시각 anchor 강화
  • PostCard / PrevNext / 관련 글 카드 통합 — 한 컴포넌트로 variant 분리

이건 다 큰 효과보다 작은 비용이 더 큰 것들이라 우선순위 뒤로 미뤘다.

작은 단서들이 합쳐지면

각 변경은 한 줄에서 수십 줄. 토큰화·breadcrumb·active state·site-map·focus·link styling — 어느 하나도 변경이 아니다. 다만 사이트 전체가 같은 어휘 를 쓰기 시작하니까, 사용자 입장에서 "어디 있는지 안다 / 다음 갈 곳을 안다 / 무엇이 변하지 않는지 안다" 가 답이 나오기 시작한다.

NN/g 가 반복해서 강조하는 말 — "UX 는 한 번의 큰 결정이 아니라 수많은 작은 결정의 누적이다". 이번 라운드는 그 말이 어떤 의미인지 직접 체감한 작업이었다.

다음 라운드는 Plan B — Cmd+K, 다크 모드, 모바일 TOC. 한 번에 하나씩.

관련 글

태그가 겹치는 다른 글