Claude Theme README
적용 디렉터리:
themes/claude. 일부 수정은 전역 파일인pages/_app.js,pages/index.js,components/SEO.js에 걸쳐 있습니다.이 문서는 현재
claude테마의 실제 구현을 설명하며, 특히 다음 내용을 다룹니다.
- 테마 특성과 시각 디자인 목표
- 글 페이지, Claude Code Docs 스타일, 및 홈, GitHub Profile 스타일
- 모바일 재현과 최적화 전략
- Contribution 히트맵 생성 로직
- 데이터베이스 설계와 업데이트 로직
- 캐시 설계
- 설정 항목과 환경 변수 설명
- 이 테마를 활성화하고 사용하는 방법
- 사이드바 지속성 아키텍처, 전역
_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.
빠른 시작
세 단계만으로 체험할 수 있습니다.
환경 변수 설정
.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>개인 프로필 페이지 생성 Notion에서 새 페이지를 만들고 slug를
readme.md로 설정합니다. 이 콘텐츠가 홈에 표시됩니다.실행
bashyarn 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을 제공합니다.
- Notion
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 Monofallback 체인
- 제목:
- 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
위에서 아래로 주요 블록은 다음과 같습니다.
- README 카드,
README.md탭 헤더 + 콘텐츠 영역 - Contribution 히트맵 블록
- 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또는updaterepositoryIdtimestampMstitle/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 === 12:count >= 23:count >= 34:count >= 6
설명: 1 contribution/day는 항상 같은 색상 등급에 안정적으로 매핑되어야 합니다.
6.4 월 라벨 전략
기본 rolling year 모드:
- 주 열의 시작 날짜가 속한 월을 기준으로 marker를 생성합니다. GitHub 시각 규칙에 가깝습니다.
고정 연도 모드:
- 각 월의 첫날이 포함된 주 열에서 marker를 생성합니다.
6.5 상호작용
- 셀에 hover하면 tooltip을 표시합니다. 흔들림을 줄이기 위해 지연 트리거를 사용합니다.
- 특정 날짜를 클릭하면 아래 activity를 해당 날짜로 필터링합니다. 다시 클릭하면 해제합니다.
Less/Morelegend는 히트맵 색상 등급과 일치합니다.
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_v1claude_contribution_snapshots_v1
8.1 권장 테이블 구조
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.js의 getStaticProps.
9.1 동기화 단계
- 게시된 글을 가져옵니다.
readme.md는 제외합니다. - 글마다 snapshot을 구성합니다.
repositoryIdcreatedAtMsupdatedAtMs
syncContributionSnapshots(snapshots)를 호출합니다.- snapshot을 upsert합니다. 충돌 키는
repository_id입니다. - "새 snapshot과 기존 snapshot의 차이"를 기준으로 이벤트를 생성합니다.
- snapshot을 upsert합니다. 충돌 키는
listContributionEvents(limit)로 이벤트를 가져옵니다.- "어제까지" 필터링합니다.
filterContributionEventsUntilYesterday- 당일 이벤트는 홈에 표시하지 않습니다. UI 안정성을 유지하고 당일 여러 번 새로고침할 때 흔들림을 줄이기 위한 설계입니다.
- 로컬 일 단위 캐시에 기록하고 프런트엔드로 반환합니다.
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} - 캐시 내용:
bodyFingerprintexcerptreadmeHtmlreadmeHtmlSource
- 로직:
- 본문 fingerprint가 바뀌지 않았다면 캐시된 HTML을 바로 재사용합니다.
- 바뀌었다면 변환과 렌더링을 다시 실행합니다.
10.3 GitHub Markdown API 캐시
위치: lib/db/notion/notionBlocksToHtml.js
- 키:
readme_github_md_${md5(markdown)} - 전략:
- 먼저 캐시를 확인하고, hit이면 바로 반환합니다.
- GitHub
/markdownAPI를 호출합니다. - API 실패/제한 초과 시 캐시를 한 번 더 확인합니다.
- 그래도 실패하면 로컬
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 관련 문제를 피합니다.
현재 구현:
- Notion에서
readme.md의blockMap을 가져옵니다. notionBlocksToMarkdown(blockMap, pageId)로 Markdown으로 변환합니다.renderMarkdownToHtml(markdown)로 HTML로 변환합니다.- GitHub API 우선.
- 실패하면 로컬 fallback.
- 프런트엔드
ProfileHome이 직접 렌더링합니다.<div className="markdown-body" dangerouslySetInnerHTML={{ __html: readmeHtml }} />
스타일 출처:
styles/claude-readme.css가 GitHub Markdown CSS를 가져옵니다._app.js가highlight.js테마를 가져옵니다. 로컬 fallback 시 적용됩니다.
12. 설정 항목 설명, Claude 테마
설정 파일: themes/claude/config.js
아래 설정은 환경 변수, NEXT_PUBLIC_*, 로 덮어쓸 수 있고, Notion 설정 페이지의 같은 이름 항목으로 다시 덮어쓸 수 있습니다.
| 설정 항목 | 기본값 | 설명 |
|---|---|---|
CLAUDE_BLOG_NAME | 활자 인쇄 | 테마 주 제목 |
CLAUDE_BLOG_NAME_EN | 주 제목과 같음 | 부제목/영문 제목 |
CLAUDE_POST_AD_ENABLE | false | 목록에 광고 삽입 |
CLAUDE_POST_COVER_ENABLE | false | 목록에 커버 표시 |
CLAUDE_ARTICLE_RECOMMEND_POSTS | true | 글 페이지 추천 글 |
CLAUDE_MENU_CATEGORY | true | 카테고리 메뉴 표시 |
CLAUDE_MENU_TAG | true | 태그 메뉴 표시 |
CLAUDE_MENU_ARCHIVE | true | 아카이브 메뉴 표시 |
CLAUDE_TOC_ENABLE | true | 오른쪽 목차 활성화 |
CLAUDE_TOC_SHOW_LEVEL3 | true | 목차에 3단계 제목 표시 |
CLAUDE_TOC_SCROLL_BEHAVIOR | instant | TOC 클릭/연동 스크롤 동작 |
CLAUDE_SUBTITLE_DARK_ONLY | false | 부제목을 다크 모드에서만 표시 |
CLAUDE_PROFILE_AVATAR | '' | 사이드바 아바타 URL |
CLAUDE_FOOTER_COPYRIGHT | '' | 사용자 지정 푸터 저작권 문구 |
CLAUDE_README_CACHE_ENABLED | true | README snapshot 캐시 스위치 |
CLAUDE_CONTRIBUTION_PERSIST_ENABLED | true | Contribution 영구 저장 스위치 |
CLAUDE_CONTRIBUTION_EVENT_LIMIT | 50000 | 가져올 이벤트 상한 |
13. 환경 변수 설명
13.1 필수, 최소 실행
NEXT_PUBLIC_THEME=claude
NOTION_PAGE_ID=<your notion database/page id>13.2 Notion 접근, 선택 사항이며 비공개 데이터베이스에서 자주 사용
NOTION_TOKEN_V2=<token_v2>
NOTION_ACTIVE_USER=<optional>설명:
NOTION_TOKEN_V2: 공개되지 않은 Notion 데이터에 접근하는 데 사용합니다.NOTION_ACTIVE_USER: 선택 사항입니다. 비워도 token으로 동작할 수 있습니다. Notion 쪽 권한에 따라 달라집니다.
13.3 Contribution 영구 저장, Supabase
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=추가 제어 항목:
CLAUDE_CONTRIBUTION_FORCE_REFRESH=false
CLAUDE_CONTRIBUTION_TRIGGER_TOKEN=<optional token>13.4 README 캐시와 사이트 전체 캐시
NEXT_PUBLIC_CLAUDE_README_CACHE_ENABLED=true
ENABLE_CACHE=true
REDIS_URL=<optional>14. 이 테마를 사용하는 방법
단계 1: 테마 전환
.env.local에서:
NEXT_PUBLIC_THEME=claude단계 2: 홈 README 페이지 준비
Notion에 페이지를 준비하고 slug를 반드시 다음으로 설정합니다.
readme.md홈은 이 페이지를 자동으로 인식해 README 카드로 렌더링합니다.
단계 3: 선택 사항으로 Contribution 영구 저장 활성화
- Supabase에 두 테이블을 만듭니다. 8절 SQL 참고.
- Supabase 환경 변수를 설정합니다.
NEXT_PUBLIC_CLAUDE_CONTRIBUTION_PERSIST_ENABLED=true를 켭니다.
단계 4: 실행
yarn dev프로덕션 빌드:
yarn build
yarn start단계 5: 기여 새로고침 수동 트리거, 선택 사항
인터페이스: /api/claude/contribution-refresh
예시:
curl "http://localhost:3000/api/claude/contribution-refresh?token=<token>&revalidate=1&path=/"15. 실행 로그와 문제 해결
15.1 로그 접두사
- Contribution:
[Contrib] ... - README 렌더링:
[README] ...
주의: 이 로그는 서버 터미널에 출력되며 브라우저 콘솔에는 표시되지 않습니다.
15.2 자주 묻는 문제
- 히트맵에 데이터가 없음
NEXT_PUBLIC_CLAUDE_CONTRIBUTION_PERSIST_ENABLED가true인지 확인합니다.- Supabase 연결 변수가 올바른지 확인합니다.
- 두 테이블이 생성되어 있는지 확인합니다.
- 당일 업데이트가 표시되지 않음
- 현재 로직은 기본적으로 "오늘"의 이벤트를 필터링하고 어제까지의 이벤트만 표시합니다. 의도된 동작입니다.
- 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테마 디렉터리 안의 변경이 아닙니다. 병합할 때 이 파일을 특히 확인하세요.
기존 코드에는 두 가지 문제가 있었습니다.
theme의useMemo가 전체route객체,[route], 에 의존했습니다.useRouter()는 라우트가 바뀔 때마다 새 객체 참조를 반환하므로theme가 매번 다시 계산됩니다.GLayout은 컴포넌트 내부에서useCallback으로 정의한 래퍼 컴포넌트였고, 렌더링될 때마다 내부에서getBaseLayoutByTheme(theme)를 호출했습니다.
수정 후:
// 전체 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)로 감쌉니다.
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 상태가 아님, 에 저장됩니다.
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.jslib/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가 더 이상 나타나지 않습니다.
- RSS를 비활성화하면 더 이상
19.2 홈 탭 제목에 부제목을 더 이상 붙이지 않음
- 파일:
components/SEO.js - 라우트:
/, 홈. - 변경: 홈 title이
site title | site description에서site title만 표시하도록 바뀌었습니다. - 결과:
- 부제목을 설정하지 않았을 때 기본 문구 "NotionNext가 생성한 사이트입니다"가 더 이상 나타나지 않습니다.
- 구분자
|도 표시되지 않습니다.
19.3 ⚠️ 병합 / 업그레이드 주의, 요약
이후 upstream 업데이트를 병합할 때는 다음 항목이 계속 유지되는지 확인하세요.
pages/_app.js에서Layout이 여전히useMemo(() => getBaseLayoutByTheme(theme), [theme])로 참조를 캐시하는지 확인합니다. 16.2절 첫 번째 계층 참고.pages/_app.js에서theme의useMemo의존성이[route.asPath, pageProps?.NOTION_CONFIG?.THEME]인지 확인합니다.[route]이면 안 됩니다.pages/_app.js에서getBaseLayoutByTheme를 간접 호출하는 래퍼 컴포넌트를useCallback으로 다시 감싸지 않는지 확인합니다.pages/index.js에서 RSS 생성이 여전히ENABLE_RSS스위치의 제어를 받는지 확인합니다.components/SEO.js에서 홈 title이 여전히 주 제목만 사용하고 description을 붙이지 않는지 확인합니다.
