Skip to content

Claude Theme README

적용 디렉터리: themes/claude. 일부 수정은 전역 파일인 pages/_app.js, pages/index.js, components/SEO.js에 걸쳐 있습니다.

이 문서는 현재 claude 테마의 실제 구현을 설명하며, 특히 다음 내용을 다룹니다.

  1. 테마 특성과 시각 디자인 목표
  2. 글 페이지, Claude Code Docs 스타일, 및 홈, GitHub Profile 스타일
  3. 모바일 재현과 최적화 전략
  4. Contribution 히트맵 생성 로직
  5. 데이터베이스 설계와 업데이트 로직
  6. 캐시 설계
  7. 설정 항목과 환경 변수 설명
  8. 이 테마를 활성화하고 사용하는 방법
  9. 사이드바 지속성 아키텍처, 전역 _app.js 수정 포함. 병합 시 주의가 필요합니다.

1. 테마 포지셔닝과 핵심 특성

claude 테마는 "혼합형" 테마입니다.

  • 글 읽기 경험: Claude Code Docs의 타이포그래피와 색상 체계를 참고합니다.
  • 홈 정보 구조: GitHub 개인 프로필을 1:1에 가깝게 참고합니다. 아바타, 프로필, 연락처, 기여 히트맵, 활동 흐름을 포함합니다.
  • 상호작용 전략: 콘텐츠 우선, 낮은 방해 요소, 가벼운 동작 효과.
  • 데이터 전략: 홈 활동 데이터를 Supabase에 영구 저장할 수 있어, 매번 프런트엔드에서 즉시 추론하지 않아도 됩니다.

주요 기능:

  • 3열 레이아웃, 왼쪽 프로필 영역 / 가운데 본문 / 오른쪽 TOC.
  • 글 페이지 Notion 원본 렌더링, NotionPage, 과 테마 스타일 적용.
  • 홈 README 카드: Notion blockMap을 Markdown으로 변환한 뒤 HTML로 렌더링.
  • GitHub 스타일 Contribution 히트맵 + 활동 요약 흐름.
  • Contribution 이벤트, create / update, 영구 저장과 중복 제거.
  • 여러 계층의 캐시, 페이지 캐시, README 캐시, Contrib 일 단위 캐시, 및 실패 시 fallback.

빠른 시작

세 단계만으로 체험할 수 있습니다.

  1. 환경 변수 설정.env 또는 .env.local에 다음을 추가합니다. 전체 설정은 아래 내용을 참고하세요.

    bash
    # 테마 활성화
    NEXT_PUBLIC_THEME=claude  # 또는 Notion 설정 페이지에서 구성
    NOTION_PAGE_ID=<your-notion-page-id>
    
    # [선택] 기여 히트맵 영구 저장 활성화, 권장
    SUPABASE_URL=<your-supabase-url>
    SUPABASE_SECRET_KEY=<your-supabase-key>
    CLAUDE_CONTRIBUTION_TRIGGER_TOKEN=<secure-token-for-api>
  2. 개인 프로필 페이지 생성 Notion에서 새 페이지를 만들고 slugreadme.md로 설정합니다. 이 콘텐츠가 홈에 표시됩니다.

  3. 실행

    bash
    yarn dev

2. 디렉터리 구조와 핵심 파일

테마 핵심 파일:

  • themes/claude/index.js
    • 테마 레이아웃 진입점, LayoutBase / LayoutIndex / LayoutSlug 등.
  • themes/claude/style.js
    • 테마 변수와 전체 스타일, 데스크톱/모바일 규칙 포함.
  • themes/claude/config.js
    • 테마 설정 항목과 기본값.
  • themes/claude/components/ProfileHome.js
    • 홈 README, 히트맵, Contribution activity 로직.
  • themes/claude/components/NavBar.js
    • 왼쪽 프로필 영역, 아바타, 연락처, 탐색, 터미널 시뮬레이션 블록.
  • themes/claude/components/MenuList.js
    • 메뉴 렌더링과 icon 규칙. Notion icon 필드에 Font Awesome을 입력하는 방식 지원.
  • themes/claude/components/Catalog.js
    • 오른쪽 목차, TOC, 와 스크롤 연동.

서버 데이터 경로 관련 파일:

  • pages/index.js
    • getStaticProps: README 렌더링, Contrib 동기화, 캐시 fallback.
  • lib/server/claude/contributionStore.js
    • Supabase 읽기/쓰기, 이벤트 생성, 중복 제거, 일 단위 캐시.
  • lib/db/notion/notionBlocksToHtml.js
    • Notion blockMap -> Markdown -> HTML. GitHub API를 우선 사용하고 로컬 fallback을 제공합니다.
  • pages/api/claude/contribution-refresh.js
    • Contribution 캐시 수동 새로고침과 ISR 트리거.

3. 글 스타일, Claude Code Docs 모방

3.1 레이아웃 스타일

글 페이지는 LayoutSlug + NotionPage로 본문을 렌더링하고, 외곽은 LayoutBase가 제공합니다.

  • 왼쪽 고정 너비 프로필/탐색 영역.
  • 가운데 콘텐츠 영역, 여러 단계의 max-w-* 너비 제어.
  • 오른쪽 목차 영역. 글 페이지, 데스크톱, TOC 활성화 조건에서 표시됩니다.

3.2 스타일 체계

themes/claude/style.js에는 많은 CSS 변수가 정의되어 있습니다.

  • 색상 변수: 본문, 테두리, 강조색, 다크 모드 등.
  • 글꼴 변수:
    • 제목: Anthropic Serif Display
    • 본문: Anthropic Sans Text
    • 고정폭: JetBrains Mono fallback 체인
  • Notion 콘텐츠 영역 스타일 재정의: 링크, 인용, 코드, 표, callout, 목차 강조 등.

3.3 목차, TOC, 동작

목차 컴포넌트: themes/claude/components/Catalog.js

  • L1/L2는 항상 표시합니다.
  • L3 표시는 CLAUDE_TOC_SHOW_LEVEL3가 제어합니다.
  • 스크롤을 감지해 현재 section을 강조하고 부모 항목도 함께 연동합니다.
  • On this page 제목은 맨 위로 이동할 수 있습니다.
  • 스크롤 동작은 smooth 또는 instant를 지원합니다. 설정 키는 CLAUDE_TOC_SCROLL_BEHAVIOR입니다.

4. 홈, GitHub Profile 1:1 재현

홈 컴포넌트: themes/claude/components/ProfileHome.js

위에서 아래로 주요 블록은 다음과 같습니다.

  1. README 카드, README.md 탭 헤더 + 콘텐츠 영역
  2. Contribution 히트맵 블록
  3. Contribution activity 타임라인

왼쪽 프로필 영역 컴포넌트: themes/claude/components/NavBar.js

  • 원형 아바타, GitHub 스타일.
  • 닉네임 + Bio.
  • 연락처, GitHub / Email.
  • 탐색 메뉴, 아이콘 지원.
  • 터미널 시뮬레이션 영역:
    • 첫 줄: Last login: ... on ttys00x
    • 둘째 줄: {author}@Macintosh ~ % {blogName} + 커서
    • ResizeObserver로 글꼴 크기를 자동 조정해 가능한 한 한 줄에 표시합니다.

5. 모바일 재현과 최적화

모바일에서는 "데스크톱과 일관된 글꼴 스타일과 정보 계층"을 유지하되, 구조를 화면에 맞게 조정합니다.

  • 왼쪽 사이드바를 상단의 간소화된 탐색으로 접습니다.
  • Contribution 히트맵 컨테이너는 가로 스크롤을 허용합니다. 셀 크기를 유지하고 정사각형을 압축하지 않습니다.
  • 스크롤바는 숨기지만 스와이프는 가능합니다.
  • Year 선택기는 모바일 친화적인 드롭다운 메뉴로 바꾸고 Contribution activity 제목 행에 둡니다.
  • 좁은 화면에서 README / 활동 카드의 여백과 모서리 반경을 다시 균형 있게 조정합니다.

구현 핵심:

  • 데스크톱 셀 크기는 너비에 따라 동적으로 계산합니다.
  • 모바일은 contribCellSize=11로 고정해 글꼴/격자가 축소되어 시각 일관성이 깨지는 것을 방지합니다.

6. Contribution 히트맵 생성 로직

핵심 코드: themes/claude/components/ProfileHome.js

6.1 입력 데이터

영구 저장 이벤트, props.contributionEvents, 를 우선 사용합니다.

  • type: create 또는 update
  • repositoryId
  • timestampMs
  • title / slug

영구 저장을 사용할 수 없으면 프런트엔드에서 posts를 직접 추론하는 방식으로 fallback합니다.

  • 글마다 create, createdAt, 이벤트 하나를 생성합니다.
  • 업데이트 시간이 생성 시간과 다르면 update, updatedAt, 이벤트를 추가로 생성합니다.

6.2 통계 구간

두 가지 모드가 있습니다.

  • 기본 모드: 최근 1년, rolling window.
  • 연도 모드: 특정 연도 고정, 1월 1일부터 12월 31일까지.

구간은 전체 주 경계에 맞춥니다.

  • 시작점은 일요일에 맞춥니다.
  • 끝점은 토요일에 맞춥니다.

6.3 색상 등급, level

CONTRIBUTION_LEVEL_THRESHOLDS:

  • 0: 기여 없음
  • 1: count === 1
  • 2: count >= 2
  • 3: count >= 3
  • 4: count >= 6

설명: 1 contribution/day는 항상 같은 색상 등급에 안정적으로 매핑되어야 합니다.

6.4 월 라벨 전략

기본 rolling year 모드:

  • 주 열의 시작 날짜가 속한 월을 기준으로 marker를 생성합니다. GitHub 시각 규칙에 가깝습니다.

고정 연도 모드:

  • 각 월의 첫날이 포함된 주 열에서 marker를 생성합니다.

6.5 상호작용

  • 셀에 hover하면 tooltip을 표시합니다. 흔들림을 줄이기 위해 지연 트리거를 사용합니다.
  • 특정 날짜를 클릭하면 아래 activity를 해당 날짜로 필터링합니다. 다시 클릭하면 해제합니다.
  • Less/More legend는 히트맵 색상 등급과 일치합니다.

7. Contribution Activity 생성 로직

핵심 코드는 계속 themes/claude/components/ProfileHome.js에 있습니다.

렌더링 전략:

  • 기본적으로 "월" 단위로 그룹화합니다. 예: March 2026.
  • 특정 날짜를 선택하면 "일" 단위 그룹으로 전환합니다. 예: March 3, 2026.
  • 그룹 내부에서는 각각 집계합니다.
    • update 이벤트 -> commit summary, 저장소별 집계와 commitCount 계산
    • create 이벤트 -> created repositories 목록

표시 로직:

  • 데이터가 없으면 empty state를 표시합니다.
  • 업데이트와 생성이 있으면 요약 행을 각각 렌더링합니다.
  • 링크를 클릭하면 해당 글로 이동합니다.

8. 데이터베이스 설계, Supabase

서버 저장 구현: lib/server/claude/contributionStore.js

두 테이블을 사용합니다.

  • claude_contribution_events_v1
  • claude_contribution_snapshots_v1

8.1 권장 테이블 구조

sql
create table if not exists public.claude_contribution_events_v1 (
  event_id text primary key,
  event_type text not null check (event_type in ('create', 'update')),
  repository_id text not null,
  timestamp_ms bigint not null,
  title text default '',
  slug text default ''
);

create index if not exists idx_claude_contrib_events_ts
  on public.claude_contribution_events_v1 (timestamp_ms desc);

create index if not exists idx_claude_contrib_events_repo
  on public.claude_contribution_events_v1 (repository_id);

create table if not exists public.claude_contribution_snapshots_v1 (
  repository_id text primary key,
  title text default '',
  slug text default '',
  created_at_ms bigint not null default 0,
  updated_at_ms bigint not null default 0,
  synced_at_ms bigint not null default 0
);

create index if not exists idx_claude_contrib_snapshots_updated
  on public.claude_contribution_snapshots_v1 (updated_at_ms desc);

8.2 필드 의미

events 테이블:

  • event_id: 이벤트 기본 키. 규칙은 e_${md5(type|repositoryId|timestampMs)}입니다.
  • event_type: create / update.
  • repository_id: 글 ID 정규화 값, - 제거 + 소문자.
  • timestamp_ms: 이벤트 타임스탬프, 밀리초.
  • title / slug: 표시용 중복 정보.

snapshots 테이블:

  • repository_id: 글의 고유 식별자, 기본 키.
  • created_at_ms: 생성 시간.
  • updated_at_ms: 최근 업데이트 시간.
  • synced_at_ms: 이번 동기화 시간.

9. 업데이트 로직, Notion에서 데이터베이스까지

진입점: pages/index.jsgetStaticProps.

9.1 동기화 단계

  1. 게시된 글을 가져옵니다. readme.md는 제외합니다.
  2. 글마다 snapshot을 구성합니다.
    • repositoryId
    • createdAtMs
    • updatedAtMs
  3. syncContributionSnapshots(snapshots)를 호출합니다.
    • snapshot을 upsert합니다. 충돌 키는 repository_id입니다.
    • "새 snapshot과 기존 snapshot의 차이"를 기준으로 이벤트를 생성합니다.
  4. listContributionEvents(limit)로 이벤트를 가져옵니다.
  5. "어제까지" 필터링합니다.
    • filterContributionEventsUntilYesterday
    • 당일 이벤트는 홈에 표시하지 않습니다. UI 안정성을 유지하고 당일 여러 번 새로고침할 때 흔들림을 줄이기 위한 설계입니다.
  6. 로컬 일 단위 캐시에 기록하고 프런트엔드로 반환합니다.

9.2 이벤트 생성 규칙, 핵심

syncContributionSnapshots 내부:

  • snapshot이 없으면, 새 글인 경우:
    • create 이벤트를 생성합니다. 시간은 created_at_ms.
    • updated_at_ms > created_at_ms이면 update 이벤트도 생성합니다.
  • snapshot이 이미 있으면:
    • updated_at_ms가 기존 snapshot보다 클 때만 새 update 이벤트를 추가합니다.
  • 이벤트를 쓰기 전에 event_id 기준으로 중복을 제거해 같은 논리 이벤트가 하나만 존재하도록 보장합니다.

10. 캐시 설계

이 테마는 중복 요청을 줄이고 안정성을 높이기 위해 여러 계층의 캐시를 사용합니다.

10.1 Contribution 일 단위 캐시, 프로세스 내부

위치: lib/server/claude/contributionStore.js

  • 캐시 키: globalThis.__claude_contribution_daily_cache_v1
  • 내용: dayKey / events / updatedAtMs / dirty
  • 새로고침 조건:
    • 수동 강제, CLAUDE_CONTRIBUTION_FORCE_REFRESH=true
    • build/export 단계
    • 당일 아직 새로고침하지 않음
    • API를 통해 dirty로 표시됨

실패 시 fallback:

  • 새로고침 실패 시 stale 캐시를 우선 사용합니다. allowStale=true.
  • 그래도 사용할 수 없으면 프런트엔드 즉시 계산으로 fallback합니다.

10.2 README snapshot 캐시

위치: pages/index.js

  • 키: readme_render_snapshot_v2_${pageId}_${locale}
  • 캐시 내용:
    • bodyFingerprint
    • excerpt
    • readmeHtml
    • readmeHtmlSource
  • 로직:
    • 본문 fingerprint가 바뀌지 않았다면 캐시된 HTML을 바로 재사용합니다.
    • 바뀌었다면 변환과 렌더링을 다시 실행합니다.

10.3 GitHub Markdown API 캐시

위치: lib/db/notion/notionBlocksToHtml.js

  • 키: readme_github_md_${md5(markdown)}
  • 전략:
    1. 먼저 캐시를 확인하고, hit이면 바로 반환합니다.
    2. GitHub /markdown API를 호출합니다.
    3. API 실패/제한 초과 시 캐시를 한 번 더 확인합니다.
    4. 그래도 실패하면 로컬 marked + highlight.js로 fallback합니다.

설명: GitHub 익명 인터페이스에는 속도 제한이 있습니다. 이 캐시는 제한에 걸릴 가능성을 크게 낮추기 위한 계층입니다.

10.4 사이트 전체 캐시 백엔드

통합 캐시 facade: lib/cache/cache_manager.js

  • Redis 우선, REDIS_URL.
  • 아니면 파일 캐시, ENABLE_FILE_CACHE.
  • 아니면 메모리 캐시, 개발 환경 120분, 프로덕션 10분.

11. README 렌더링 경로

목표: 홈 README 카드에서 rich text와 코드 하이라이트를 안정적으로 표시하고 hydration 관련 문제를 피합니다.

현재 구현:

  1. Notion에서 readme.mdblockMap을 가져옵니다.
  2. notionBlocksToMarkdown(blockMap, pageId)로 Markdown으로 변환합니다.
  3. renderMarkdownToHtml(markdown)로 HTML로 변환합니다.
    • GitHub API 우선.
    • 실패하면 로컬 fallback.
  4. 프런트엔드 ProfileHome이 직접 렌더링합니다.
    • <div className="markdown-body" dangerouslySetInnerHTML=&#123;&#123; __html: readmeHtml &#125;&#125; />

스타일 출처:

  • styles/claude-readme.css가 GitHub Markdown CSS를 가져옵니다.
  • _app.jshighlight.js 테마를 가져옵니다. 로컬 fallback 시 적용됩니다.

12. 설정 항목 설명, Claude 테마

설정 파일: themes/claude/config.js

아래 설정은 환경 변수, NEXT_PUBLIC_*, 로 덮어쓸 수 있고, Notion 설정 페이지의 같은 이름 항목으로 다시 덮어쓸 수 있습니다.

설정 항목기본값설명
CLAUDE_BLOG_NAME활자 인쇄테마 주 제목
CLAUDE_BLOG_NAME_EN주 제목과 같음부제목/영문 제목
CLAUDE_POST_AD_ENABLEfalse목록에 광고 삽입
CLAUDE_POST_COVER_ENABLEfalse목록에 커버 표시
CLAUDE_ARTICLE_RECOMMEND_POSTStrue글 페이지 추천 글
CLAUDE_MENU_CATEGORYtrue카테고리 메뉴 표시
CLAUDE_MENU_TAGtrue태그 메뉴 표시
CLAUDE_MENU_ARCHIVEtrue아카이브 메뉴 표시
CLAUDE_TOC_ENABLEtrue오른쪽 목차 활성화
CLAUDE_TOC_SHOW_LEVEL3true목차에 3단계 제목 표시
CLAUDE_TOC_SCROLL_BEHAVIORinstantTOC 클릭/연동 스크롤 동작
CLAUDE_SUBTITLE_DARK_ONLYfalse부제목을 다크 모드에서만 표시
CLAUDE_PROFILE_AVATAR''사이드바 아바타 URL
CLAUDE_FOOTER_COPYRIGHT''사용자 지정 푸터 저작권 문구
CLAUDE_README_CACHE_ENABLEDtrueREADME snapshot 캐시 스위치
CLAUDE_CONTRIBUTION_PERSIST_ENABLEDtrueContribution 영구 저장 스위치
CLAUDE_CONTRIBUTION_EVENT_LIMIT50000가져올 이벤트 상한

13. 환경 변수 설명

13.1 필수, 최소 실행

bash
NEXT_PUBLIC_THEME=claude
NOTION_PAGE_ID=<your notion database/page id>

13.2 Notion 접근, 선택 사항이며 비공개 데이터베이스에서 자주 사용

bash
NOTION_TOKEN_V2=<token_v2>
NOTION_ACTIVE_USER=<optional>

설명:

  • NOTION_TOKEN_V2: 공개되지 않은 Notion 데이터에 접근하는 데 사용합니다.
  • NOTION_ACTIVE_USER: 선택 사항입니다. 비워도 token으로 동작할 수 있습니다. Notion 쪽 권한에 따라 달라집니다.

13.3 Contribution 영구 저장, Supabase

bash
NEXT_PUBLIC_CLAUDE_CONTRIBUTION_PERSIST_ENABLED=true
NEXT_PUBLIC_CLAUDE_CONTRIBUTION_EVENT_LIMIT=50000

SUPABASE_URL=<https://xxx.supabase.co>
SUPABASE_SECRET_KEY=<service key> # 또는 SUPABASE_SERVICE_ROLE_KEY

# 선택 사항: 프런트엔드 이름 fallback
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=

추가 제어 항목:

bash
CLAUDE_CONTRIBUTION_FORCE_REFRESH=false
CLAUDE_CONTRIBUTION_TRIGGER_TOKEN=<optional token>

13.4 README 캐시와 사이트 전체 캐시

bash
NEXT_PUBLIC_CLAUDE_README_CACHE_ENABLED=true
ENABLE_CACHE=true
REDIS_URL=<optional>

14. 이 테마를 사용하는 방법

단계 1: 테마 전환

.env.local에서:

bash
NEXT_PUBLIC_THEME=claude

단계 2: 홈 README 페이지 준비

Notion에 페이지를 준비하고 slug를 반드시 다음으로 설정합니다.

text
readme.md

홈은 이 페이지를 자동으로 인식해 README 카드로 렌더링합니다.

단계 3: 선택 사항으로 Contribution 영구 저장 활성화

  1. Supabase에 두 테이블을 만듭니다. 8절 SQL 참고.
  2. Supabase 환경 변수를 설정합니다.
  3. NEXT_PUBLIC_CLAUDE_CONTRIBUTION_PERSIST_ENABLED=true를 켭니다.

단계 4: 실행

bash
yarn dev

프로덕션 빌드:

bash
yarn build
yarn start

단계 5: 기여 새로고침 수동 트리거, 선택 사항

인터페이스: /api/claude/contribution-refresh

예시:

bash
curl "http://localhost:3000/api/claude/contribution-refresh?token=<token>&revalidate=1&path=/"

15. 실행 로그와 문제 해결

15.1 로그 접두사

  • Contribution: [Contrib] ...
  • README 렌더링: [README] ...

주의: 이 로그는 서버 터미널에 출력되며 브라우저 콘솔에는 표시되지 않습니다.

15.2 자주 묻는 문제

  1. 히트맵에 데이터가 없음
  • NEXT_PUBLIC_CLAUDE_CONTRIBUTION_PERSIST_ENABLEDtrue인지 확인합니다.
  • Supabase 연결 변수가 올바른지 확인합니다.
  • 두 테이블이 생성되어 있는지 확인합니다.
  1. 당일 업데이트가 표시되지 않음
  • 현재 로직은 기본적으로 "오늘"의 이벤트를 필터링하고 어제까지의 이벤트만 표시합니다. 의도된 동작입니다.
  1. README 코드 블록 하이라이트가 불안정함
  • GitHub /markdown 제한을 초과하면 로컬 marked + highlight.js로 자동 fallback합니다.
  • 이전에 성공한 렌더링 결과를 캐시로 재사용할 수 있습니다.

16. 사이드바 지속성 아키텍처

16.1 문제 배경

Next.js Pages Router에서는 클라이언트 내비게이션, 링크 클릭 이동, 마다 LayoutBase가 다시 렌더링되거나 심지어 다시 마운트될 수 있습니다. 이로 인해 왼쪽 사이드바, 아바타, 터미널 시뮬레이션 블록, 탐색 메뉴가 페이지 이동마다 다시 로드되어 사용자 경험이 나빠집니다.

16.2 세 계층 방어 방안

이 테마는 세 계층의 메커니즘으로 왼쪽 사이드바가 브라우저 새로고침 시에만 다시 로드되도록 합니다.

첫 번째 계층: pages/_app.js - 근본적으로 Layout 컴포넌트 remount 제거

⚠️ 병합 주의: 이 수정은 전역 pages/_app.js에 있으며 claude 테마 디렉터리 안의 변경이 아닙니다. 병합할 때 이 파일을 특히 확인하세요.

기존 코드에는 두 가지 문제가 있었습니다.

  1. themeuseMemo가 전체 route 객체, [route], 에 의존했습니다. useRouter()는 라우트가 바뀔 때마다 새 객체 참조를 반환하므로 theme가 매번 다시 계산됩니다.
  2. GLayout은 컴포넌트 내부에서 useCallback으로 정의한 래퍼 컴포넌트였고, 렌더링될 때마다 내부에서 getBaseLayoutByTheme(theme)를 호출했습니다.

수정 후:

javascript
// 전체 route 객체가 아니라 구체적인 값에 의존합니다.
const theme = useMemo(() => {
  return (
    getQueryParam(route.asPath, 'theme') ||
    pageProps?.NOTION_CONFIG?.THEME ||
    BLOG.THEME
  )
}, [route.asPath, pageProps?.NOTION_CONFIG?.THEME])

// useMemo로 Layout 컴포넌트 참조를 캐시합니다. 같은 theme에서는 항상 같은 컴포넌트를 반환합니다.
const Layout = useMemo(() => getBaseLayoutByTheme(theme), [theme])

// GLayout 래퍼를 거치지 않고 Layout을 직접 사용합니다.
<Layout {...pageProps}>
  <SEO {...pageProps} />
  <Component {...pageProps} />
</Layout>

핵심 효과: Layout, 즉 LayoutBase, 이 같은 테마에서는 항상 같은 컴포넌트 참조가 됩니다. React는 컴포넌트 타입 변화로 전체 하위 트리를 remount하지 않습니다.

두 번째 계층: themes/claude/index.js - SidebarContent 메모이제이션

데스크톱 사이드바는 React.memo(() => true)로 감쌉니다.

javascript
const SidebarContent = memo(function SidebarContent(props) {
  return (
    <div className='flex flex-col justify-between h-full py-6 px-5'>
      <div><NavBar {...props} /></div>
      <div className='mt-auto'><Footer /></div>
    </div>
  )
}, () => true)  // 항상 true를 반환해 부모 컴포넌트의 모든 prop 변화가 재렌더링을 유발하지 않게 합니다.
  • React.memo의 두 번째 인수 () => true는 "props가 항상 같다"는 의미이므로, 부모 컴포넌트의 re-render 전파를 막습니다.
  • MenuList 내부의 useRouter()는 React Context 기반이므로 라우트 변화가 memo를 우회해 메뉴 활성 상태를 정상 업데이트합니다.

세 번째 계층: themes/claude/components/NavBar.js - 모듈 수준 터미널 세션 캐시

터미널 영역의 로그인 시간과 tty 번호는 JS 모듈 수준 변수, React 상태가 아님, 에 저장됩니다.

javascript
let _cachedTerminalSession = null
function getOrCreateTerminalSession() {
  if (!_cachedTerminalSession) {
    _cachedTerminalSession = {
      loginTime: formatTerminalLoginTime(new Date()),
      tty: `ttys00${Math.floor(Math.random() * 10)}`
    }
  }
  return _cachedTerminalSession
}
  • 모듈 수준 변수는 JS 모듈 스코프에 있으며 어떤 React 컴포넌트 인스턴스에도 속하지 않습니다.
  • 극단적인 상황에서 컴포넌트가 remount되더라도 캐시 값은 사라지지 않습니다.
  • 브라우저 새로고침, JS 모듈 재로드, 때에만 재설정됩니다.

16.3 관련 파일 목록

파일범위수정 내용
pages/_app.js전역, 테마 디렉터리 아님GLayout 제거, useMemo로 Layout 참조 캐시
themes/claude/index.js테마SidebarContent memo 컴포넌트 추가
themes/claude/components/NavBar.js테마터미널 세션을 모듈 수준 캐시로 변경

17. 설계 제약과 알려진 동작

  • claude 테마는 자체 테마 디렉터리에만 영향을 주지만, 사이드바 지속성 수정은 전역 pages/_app.js를 포함합니다. 16절 참고.
  • Contribution 이벤트는 멱등 쓰기이므로 같은 create 이벤트가 중복 생성되면 안 됩니다.
  • README 렌더링은 "서버 변환 + 프런트엔드 정적 HTML 주입"을 사용하며, 안정성을 우선합니다.
  • 모바일은 데스크톱 시각 언어와의 일관성을 우선 유지하며, 화면 너비에 따라 글꼴 두께/크기를 자동으로 낮추지 않습니다.

18. 유지보수 권장사항

  • 히트맵 규칙을 수정했다면 다음을 함께 업데이트하세요.
    • themes/claude/components/ProfileHome.js
    • 이 README의 6, 7절
  • 테이블 필드를 수정했다면 다음을 함께 업데이트하세요.
    • lib/server/claude/contributionStore.js
    • 이 README의 8, 9절 SQL과 필드 설명
  • 캐시 전략을 수정했다면 다음을 함께 업데이트하세요.
    • lib/cache/*
    • pages/index.js
    • lib/db/notion/notionBlocksToHtml.js

19. 전역 변경 보충, RSS와 홈 제목

다음 변경은 테마 디렉터리 밖에 있지만 claude 테마의 실제 런타임 동작에 직접 영향을 줍니다.

19.1 RSS가 꺼져 있을 때 RSS 콘텐츠 가져오기를 더 이상 실행하지 않음

  • 파일: pages/index.js
  • 변경: generateRss(props)가 "무조건 실행"에서 "ENABLE_RSS=true일 때만 실행"으로 바뀌었습니다.
  • 결과:
    • RSS를 비활성화하면 더 이상 getPostBlocks(..., 'rss-content')를 호출하지 않습니다.
    • 서버 로그에 from:rss-content가 더 이상 나타나지 않습니다.

19.2 홈 탭 제목에 부제목을 더 이상 붙이지 않음

  • 파일: components/SEO.js
  • 라우트: /, 홈.
  • 변경: 홈 title이 site title | site description에서 site title만 표시하도록 바뀌었습니다.
  • 결과:
    • 부제목을 설정하지 않았을 때 기본 문구 "NotionNext가 생성한 사이트입니다"가 더 이상 나타나지 않습니다.
    • 구분자 |도 표시되지 않습니다.

19.3 ⚠️ 병합 / 업그레이드 주의, 요약

이후 upstream 업데이트를 병합할 때는 다음 항목이 계속 유지되는지 확인하세요.

  1. pages/_app.js에서 Layout이 여전히 useMemo(() => getBaseLayoutByTheme(theme), [theme])로 참조를 캐시하는지 확인합니다. 16.2절 첫 번째 계층 참고.
  2. pages/_app.js에서 themeuseMemo 의존성이 [route.asPath, pageProps?.NOTION_CONFIG?.THEME]인지 확인합니다. [route]이면 안 됩니다.
  3. pages/_app.js에서 getBaseLayoutByTheme를 간접 호출하는 래퍼 컴포넌트를 useCallback으로 다시 감싸지 않는지 확인합니다.
  4. pages/index.js에서 RSS 생성이 여전히 ENABLE_RSS 스위치의 제어를 받는지 확인합니다.
  5. components/SEO.js에서 홈 title이 여전히 주 제목만 사용하고 description을 붙이지 않는지 확인합니다.