Giscus 댓글의 commentId 기반 매칭
변경 요약
- Giscus 매핑을
mapping: 'specific'로 사용 시 각 문서의 FrontmattercommentId를 강제하여, 문서 이동/제목 변경과 무관하게 동일한 토론 스레드를 유지. - SPA 내비게이션 시 기존 giscus iframe/script를 정리하고,
data-term이 없으면 위젯 삽입을 건너뛰도록 안전장치 추가. - 레이아웃에서
commentId가 없는 문서는 댓글 영역 자체를 렌더링하지 않도록 가드.
변경된 파일
quartz/components/Comments.tsxquartz/components/scripts/comments.inline.ts- (레이아웃 사용)
quartz.layout.ts
주요 구현 포인트 및 실제 변경 코드
1) quartz/components/Comments.tsx: commentId 강제 및 data-term 전달
const mapping = opts.options.mapping ?? "url"
const commentId = fileData.frontmatter?.commentId as string | undefined
if (mapping === "specific" && !commentId) {
const identifier = fileData.filePath ?? fileData.slug ?? "unknown"
console.warn(`[Quartz] Missing commentId for page ${identifier}, skipping giscus mounting.`)
return <></>
}
return (
<div
class={classNames(displayClass, "giscus")}
...
data-mapping={mapping}
data-term={mapping === "specific" ? commentId : undefined}
...
></div>
)핵심:
mapping === 'specific'인데commentId가 없으면 경고를 남기고 컴포넌트를 렌더링하지 않음.- 유효한 경우에만
data-term으로commentId를 giscus에 전달.
2) quartz/components/scripts/comments.inline.ts: SPA 재장착 + 불완전 매핑 차단
document.addEventListener("nav", () => {
const giscusContainer = document.querySelector(".giscus") as GiscusElement
if (!giscusContainer) return
giscusContainer
.querySelectorAll("iframe.giscus-frame, script[src*='giscus.app']")
.forEach((node) => node.remove())
if (giscusContainer.dataset.mapping === "specific" && !giscusContainer.dataset.term) {
console.warn("[Giscus] mapping='specific' but data-term is missing; skipping widget injection.")
return
}
const giscusScript = document.createElement("script")
giscusScript.src = "https://giscus.app/client.js"
...
giscusScript.setAttribute("data-mapping", giscusContainer.dataset.mapping)
if (giscusContainer.dataset.term) {
giscusScript.setAttribute("data-term", giscusContainer.dataset.term)
}
...
giscusContainer.appendChild(giscusScript)
})핵심:
- 내비게이션 시 기존 giscus 리소스를 정리 후 재삽입.
mapping='specific'인데data-term이 비어 있으면 위젯 삽입을 건너뛰어 잘못된 스레드 연결을 방지.
3) quartz.layout.ts: 실제 적용 설정값 전체 + 렌더 가드
Component.ConditionalRender({
component: Component.Comments({
provider: "giscus",
options: {
repo: "<owner>/<repo>",
repoId: "<repoId>",
category: "<discussionCategoryName>",
categoryId: "<discussionCategoryId>",
mapping: "specific",
strict: true,
reactionsEnabled: true,
inputPosition: "top",
lang: "en",
lightTheme: "light",
darkTheme: "dark",
// themeUrl은 미지정 → cfg.baseUrl 기준으로 자동 계산됨
},
}),
condition: ({ fileData }) => Boolean(fileData.frontmatter?.commentId),
})참고:
themeUrl은 생략했으며, 런타임에서cfg.baseUrl을 기준으로https://<baseUrl>/static/giscus로 계산됩니다. 예:baseUrl: "example.com"→themeUrl = https://example.com/static/giscus.- 문서 Frontmatter에
commentId가 있을 때만 댓글 컴포넌트를 렌더링합니다. - giscus는
mapping: 'specific'로 고정하여 스레드 식별을commentId로 일원화합니다.
왜 변경했는가
- 문서 경로/제목 변경으로 기존 댓글이 분리되는 문제를 방지하고, 하나의 영구 식별자(
commentId)로 스레드를 유지하려는 목적. - SPA 내비게이션 환경에서 위젯 중복 삽입/테마 불일치 문제를 해소.
운영 체크리스트
- 공개 문서는 모두 고유
commentId를 가져야 하며, Vault 측 템플러(Templater)로 자동 발급 및 중복 검증을 수행. - 프리뷰에서 라이트/다크 테마 전환 시 giscus 테마가 동기화되는지 확인.
관련 커밋
820413dfeat(Giscus): persist discussions via commentId
commentId 자동 부여 템플러(Templater)
Obsidian Templater를 이용해 blog/ 이하의 공개 문서(frontmatter publish: true)에 commentId를 자동으로 채우고, 중복을 감지합니다. 기본적으로는 무소음으로 동작하며, 중복이 발견된 경우에만 경고를 출력합니다.
필수: .obsidian/plugins/templater/user_scripts/uuid.js (아래 참조)
동작 요약(댓글 ID 관련)
- 공개 문서에서
commentId가 비어 있으면 UUID를 생성해 채움. commentId가 숫자 또는 배열인 경우 문자열로 정규화.- 처리 이후 모든 파일의 최종
commentId를 수집하여 중복 그룹이 있으면 경고(본문 출력 + Obsidian Notice) 표시.
핵심 코드 발췌
// commentId 필요 여부와 신규 ID 생성
const needsId = !fmCache.commentId || String(fmCache.commentId).trim() === ""
const newId = needsId ? await tp.user.uuid() : null
await app.fileManager.processFrontMatter(file, (fm) => {
// commentId 보강/정규화
if (needsId) fm.commentId = newId
else if (typeof fm.commentId !== "string") fm.commentId = String(fm.commentId)
})
// 최종 commentId 추적(중복 검출용)
const finalId = (needsId ? newId : String(fmCache.commentId ?? "")).trim()
if (finalId) {
if (!idGroups.has(finalId)) idGroups.set(finalId, [])
idGroups.get(finalId).push(file.path)
}
// 중복 commentId만 경고 출력 + Notice
const dups = [...idGroups.entries()].filter(([_, list]) => list.length > 1)
if (dups.length) {
tR += `⚠️ Duplicate commentId detected (${dups.length} group${dups.length>1?'s':''}):\n` +
dups.map(([id, list]) => `- ${id}\n - ${list.join('\n - ')}`).join('\n')
try { new Notice(`giscus: duplicate commentId ${dups.length} group(s) found`) } catch(_) {}
}UUID 유저 스크립트(.obsidian/plugins/templater/user_scripts/uuid.js)
const { randomUUID } = require('crypto')
module.exports = async function uuid() {
return randomUUID()
}이 템플러로 commentId가 항상 존재하도록 보장하면, 위의 Quartz 변경사항(mapping: 'specific' + 렌더 가드)과 결합되어 문서 이동/제목 변경에도 giscus 스레드가 안정적으로 영구 매칭됩니다.

