테마 마이그레이션 가이드 (NotionNext)
이 가이드는 외부 테마, 예를 들어 Astro/Vite 테마를 NotionNext의 Next.js + Notion 데이터 구조로 옮길 때 참고하기 위한 문서입니다.
1. 마이그레이션 목표
- 원본 테마의 시각 언어, 즉 레이아웃, 간격, 카드, 동작 효과를 가능한 한 유지합니다.
- NotionNext의 데이터 흐름과 기능 규약을 따릅니다.
- 동작은 페이지 내부에 하드코딩하지 말고
themes/<theme>/config.js의 스위치로 노출합니다.
2. 권장 디렉터리 구조
새 테마에는 독립 디렉터리를 만듭니다.
themes/<theme>/index.jsthemes/<theme>/style.jsthemes/<theme>/config.jsthemes/<theme>/components/*
원칙:
- 다른 테마 디렉터리의 UI 컴포넌트를 직접 참조하지 않습니다.
- 테마 간 공통 기능은
NotionPage,Comment,ShareBar,FlipCard, 광고 컴포넌트 같은 전역 컴포넌트를 우선 재사용합니다. - 테마 전용 렌더링과 스타일은 반드시 현재 테마 디렉터리 안에 둡니다.
3. NotionNext 데이터 계약
테마 레이아웃과 컴포넌트에서 자주 사용할 수 있는 props:
siteInfo: 사이트 메타 정보, 제목, 설명, 커버 등posts,post,archivePostslatestPosts,categoryOptions,tagOptionsnoticepostCountprev,nextcustomNav,customMenurightAreaSlot
글에서 자주 사용하는 필드:
title,slug,href,summarypublishDay,lastEditedDaypageCover,pageCoverThumbnailcategory,tagItemstoc
4. 반드시 호환해야 하는 NotionNext 기능
새 테마를 마이그레이션할 때는 최소한 다음 항목을 확인합니다.
데이터 기반 메뉴
- 기본 메뉴 항목 지원
customNav지원CUSTOM_MENU + customMenu덮어쓰기 지원
공지 모듈
NotionPage로 공지 콘텐츠 렌더링- 테마 설정 스위치로 켜고 끌 수 있어야 함
Notion 커버를 Hero로 사용
- 홈 Hero는
siteInfo.pageCover를 우선 사용 - fallback 이미지 설정 지원
- 홈 Hero는
다크 모드
- 전역 context인
useGlobal/toggleDarkMode사용 - 전역 상태와 분리된 테마 전용 다크 상태를 따로 구현하지 않음
- 전역 context인
글 기능 모듈
- TOC 스위치
- 공유 스위치
- 댓글 스위치
- 저작권 정보 스위치
- 이전 글/다음 글 스위치
사이드바 모듈화
- 최신 글, 카테고리, 태그
- 연락처 카드, 선택적으로 플립 카드 사용
- 통계 카드
- 광고 카드
- 플러그인 슬롯,
rightAreaSlot
오른쪽 아래 플로팅 도구
- 맨 위로 이동
- 댓글 영역으로 이동
- 다크 모드 빠른 전환
5. 설정 설계 권장사항
일관되게 siteConfig('<KEY>', <default>, CONFIG)를 사용합니다.
권장 키 그룹:
THEME_MENU_*THEME_HERO_*THEME_POST_LIST_*THEME_WIDGET_*THEME_ARTICLE_*
컴포넌트 내부 곳곳에 매직 상수를 흩뿌리지 마세요.
6. 권장 마이그레이션 흐름
- 먼저 최소 실행 가능한 골격을 만듭니다. 예:
LayoutBase,LayoutIndex,LayoutSlug. - 큰
index.js파일을 여러 컴포넌트로 분리합니다. - 원본 테마의 시각 세부 사항을 항목별로 복원합니다. 예: 카드, Banner, 메타 정보 밀도, 전환 효과.
- NotionNext의 특화 모듈과 설정 스위치를 연결합니다.
- 새로 추가한 설정의 문서를 보강합니다.
- lint를 실행하고 주요 라우트를 검증합니다.
- 홈/목록/검색/아카이브/카테고리/태그/글/404
- 라이트/다크 모드
- 메뉴 동작,
customNav,CUSTOM_MENU - 공지, 광고, 플러그인 슬롯, 연락처 카드
7. 연락처 이메일 (CONTACT_EMAIL) 규약
환경 변수 NEXT_PUBLIC_CONTACT_EMAIL은 빌드 단계에서 conf/contact.config.js에 기록되고, siteConfig('CONTACT_EMAIL')에는 Base64 형식으로 저장됩니다. 정적 페이지에 이메일 평문이 직접 노출되는 것을 피하기 위한 처리입니다. 테마를 마이그레이션하거나 새로 추가할 때는 용도에 맞는 방식을 반드시 선택하세요. 그렇지 않으면 mailto가 깨지거나 Gravatar가 일치하지 않는 문제가 생길 수 있습니다.
| 상황 | 올바른 방식 | 잘못된 예 |
|---|---|---|
| 아이콘/링크 클릭으로 시스템 메일 클라이언트 열기 | handleEmailClick 사용, 아래 예시 참고 | href={\mailto:${siteConfig('CONTACT_EMAIL')}`}` |
| 푸터나 문구에 이메일 주소 표시 | resolveContactEmail(siteConfig('CONTACT_EMAIL')) | siteConfig('CONTACT_EMAIL') 직접 렌더링 |
| Gravatar 등에 필요한 이메일 md5 | resolveContactEmail 결과를 소문자로 만든 뒤 해시 | 암호화 문자열에 md5 적용 |
security.txt 같은 서버 생성 연락처 행 | resolveContactEmail 후 기록 | 암호화 문자열을 mailto:에 기록 |
메일 클릭 패턴, themes/next/components/SocialButton.js와 맞추는 것을 권장합니다.
import { useRef } from 'react'
import { handleEmailClick } from '@/lib/plugins/mailEncrypt'
import { siteConfig } from '@/lib/config'
const emailIcon = useRef(null)
const CONTACT_EMAIL = siteConfig('CONTACT_EMAIL')
// ...
{CONTACT_EMAIL && (
<a
onClick={e => handleEmailClick(e, emailIcon, CONTACT_EMAIL)}
title='email'
className='cursor-pointer'
ref={emailIcon}>
{/* icon */}
</a>
)}구현은 lib/plugins/mailEncrypt.js에 있으며, handleEmailClick, decryptEmail, resolveContactEmail을 제공합니다.
8. 메인 저장소에 테마 기여하기: 미리보기 이미지와 소개 문구
로컬이나 직접 만든 fork에서는 언제든지 사용자 지정 테마를 themes/<테마 디렉터리명>/ 아래에 두고 사용할 수 있으며, NotionNext upstream에 반드시 제출할 필요는 없습니다.
테마를 NotionNext 공식 메인 저장소에 병합하고 싶다면 전체 테마 코드 외에도 THEME_SWITCH / NEXT_PUBLIC_THEME_SWITCH를 켰을 때 표시되는 테마 전환 플로팅 창의 미리보기 이미지와 소개 문구를 함께 보강해야 합니다.
8.1 미리보기 이미지 디렉터리와 이름, 고정 규칙
미리보기 정적 리소스는 다음 저장소 디렉터리에 커밋합니다.
public/images/themes-preview/
| 파일 | 설명 |
|---|---|
<테마 디렉터리명>.png | 일반 규약에서 제공해야 하는 기준 이미지입니다. themes/ 아래 폴더명과 일치해야 하며 소문자를 사용합니다. 예: endspace.png. LazyImage의 **fallbackSrc**로 사용되어 호환성을 보장합니다. |
<테마 디렉터리명>.webp | 함께 제공하는 것을 강력히 권장합니다. PNG에서 내보내거나 변환하면 됩니다. 우선 **src**로 사용되어 용량이 더 작고 더 빠르게 로드됩니다. cwebp, Squoosh, ImageMagick 등으로 PNG를 WebP로 변환할 수 있습니다. |
컴포넌트는 기본적으로 **/images/themes-preview/<id>.webp**와 **.png**를 읽습니다. 특정 형식이 없어도 패널은 동작하지만, PR에서는 가능한 한 두 형식을 모두 제공하세요.
8.2 표시 이름과 소개, 고정 설정 파일
**conf/themeSwitch.manifest.js**를 편집하고 **THEME_SWITCH_MANIFEST**에 테마 id, 즉 themes/ 아래 디렉터리명과 같은 값으로 항목을 추가합니다.
name선택 사항: 테마 전환 패널에 표시되는 제목입니다. 작성하지 않으면 디렉터리명이 읽기 좋은 제목으로 자동 변환됩니다.summary권장: 카드 제목 아래 표시되는 한 줄 소개입니다.cover/coverWebp선택 사항: 사용자 지정 미리보기 이미지 URL입니다. 작성하지 않으면 이전 절의 기본 경로를 사용합니다. 당장 PNG만 제공하고 WebP를 업로드하지 않는다면 해당 테마의 **coverWebp**를 **''**로 설정해 패널이 PNG만 사용하게 할 수 있습니다.tier선택 사항:'free'또는'paid'입니다. 기본값은 **'free'**이며 패널에 대응하는 라벨, 무료/유료가 표시됩니다. 나중에 유료 테마를 출시한다면 **tier**를 **'paid'**로 설정해 구분 표시할 수 있습니다.
읽기 로직은 같은 파일에서 내보내는 **getThemeSwitchMeta()**가 기본값과 함께 일괄 병합합니다. **components/ThemeSwitch.js**는 이 함수에만 의존하므로 테마 디렉터리 안에서 소개 정보를 중복 관리할 필요가 없습니다.
8.3 문서와 기타 규약
- 테마 상세 설명은 여전히
docs/developer/themes/아래에 작성하는 것을 권장합니다. 아래의 "고충실도 확인 목록"에 있는 문서 배치 규약을 참고하세요. - 메인 저장소에 병합하기 전 PR 설명에는 미리보기 이미지 파일과
themeSwitch.manifest.js에서 새로 추가하거나 수정한 항목을 적어 주세요.
8.4 개발자를 위한 안내: 오픈소스 기여와 상용화 계획
이 절은 테마 개발자를 대상으로 하며, 현재의 오픈소스 협업 방식과 향후 가능성이 있는 상용화 방향을 설명합니다. 구체적인 규칙과 일정은 프로젝트의 공식 공지를 기준으로 합니다.
- 투입 인정: 완성도 있는 테마에는 시각 디자인, 상호작용, 여러 화면 크기 대응, 장기 호환성 유지가 필요하며 개발과 유지 비용이 큽니다. 재배포 가능한 테마를 NotionNext 공개 메인 저장소에 기여하는 일은 커뮤니티에 중요한 지원입니다.
- 현재 기본 경로: GitHub 메인 저장소 PR을 통해 무료 사용 테마를 제출하는 방식을 계속 권장합니다. 이때 §8.1-§8.3의 미리보기와 manifest 규약을 지켜야 합니다. 로컬이나 비공개 fork에서 개인적으로 사용하는 것은 제한하지 않습니다.
- 향후 계획, 아직 적용되지 않음: 생태계가 성숙한 뒤에는 개발자가 일부 테마를 유료 형태로 제출할 수 있도록 허용하는 방안을 계획하고 있습니다. 작성자의 노동을 존중하면서 고품질 테마에 지속 가능한 경로를 제공하기 위함입니다.
- 계획 중인 형태, 예시: 이후 별도의 비공개 Git 저장소를 마련해 개발자가 테마를 업로드, 버전 관리, 제출할 수 있게 할 수 있습니다. 테마는 개발자가 직접 또는 플랫폼 지원을 통해 가격을 정할 수 있으며, 사용자는 결제 후 자신의 환경에 비공개 배포하는 데 필요한 산출물이나 라이선스를 받을 수 있습니다. 구체적인 라이선스 범위, 업데이트 정책, 기술 지원은 정식 출시 시점에 확정됩니다.
- 코드와의 연결:
conf/themeSwitch.manifest.js의tier필드,free/paid, 는 테마 전환 패널의 표시 계층에 사용됩니다. 유료 인증, 주문, 전달, 비공개 배포 흐름은 manifest만으로 완료되지 않으며, 출시 시 별도의 문서와 도구 체인이 제공됩니다.
9. Fuwari 마이그레이션 설명
themes/fuwari에는 현재 다음 사항이 적용되어 있습니다.
- upstream 스타일 참고 소스: saicaca/fuwari
- Notion 커버 Hero 지원
- 데이터 기반 메뉴,
customNav/customMenu지원 - 독립 TOC, 사이드바 모듈, 오른쪽 아래 플로팅 영역
- 전역
FlipCard기반 플립 연락처 카드
10. 자주 발생하는 문제
- 메뉴를 하드코딩해
customMenu를 연결하지 않음 - 다른 테마의 UI를 직접 재사용해 결합도가 높아짐
- 다크 모드가 전역 상태와 일치하지 않음
post?.toc,notice?.blockMap같은 null 보호가 빠짐- 새 기능을 설정 스위치로 노출하지 않음
- 연락처 이메일에
mailto:직링크로 암호문을 사용하거나handleEmailClick/resolveContactEmail을 사용하지 않음. 위 7절 참고
11. 고충실도 확인 목록, Fuwari 기준
- 레이아웃 방향: 데스크톱 기본값은 "왼쪽 기능 영역 + 오른쪽 콘텐츠 흐름"이어야 합니다.
- Hero 전체 너비: 스크롤바가 있는 상황에서
calc(50% - 50vw)가 오프셋을 만들지 않도록, 안정적인 중앙 이동 방식을 우선 사용합니다. - 글 카드 두 형태:
- 커버 있음: 왼쪽 텍스트 + 오른쪽 이미지
- 커버 없음: 오른쪽 버튼 레일을 유지해 리듬을 통일
- Readmore 정렬: 오른쪽 버튼 영역은 카드 높이에 맞춰 정렬되어야 하며 튐 현상이 없어야 합니다.
- 사이드바 개인 카드: 원본 테마에 소셜 버튼 행이 있다면 아바타/소개 아래에 보강합니다.
- 테마 색상 팔레트 경험:
- 오른쪽 위 색상 버튼에서 열림
- 사이드바가 아니라 플로팅 패널 사용
- 실시간 미리보기 + 로컬 기억
- 복사 가능한 hue/hex 표시, 사이트 관리자가
config.js에 다시 입력하기 쉬워야 함
- 테마 문서 위치:
- Markdown 문서를
themes/<theme>/아래에 직접 두지 않습니다. 일부 빌드 경로는 테마 디렉터리를 런타임 모듈로 처리할 수 있습니다. - 테마 설명은
docs/developer/themes/아래에 통일해 두는 것을 권장합니다.
- Markdown 문서를
- 페이지 전환 체감: 가벼운 전환 효과를 보강해 원본 테마의 상호작용 리듬에 가깝게 맞춥니다.
