From b7d65eafba36dde292980648c396d65c62e909c4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Dec 2025 22:44:25 +0000
Subject: [PATCH 1/5] Initial plan
From 690b87853781315fd8c77cd6829bf8b09595d67d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Dec 2025 22:51:41 +0000
Subject: [PATCH 2/5] Migrate from Utterances to Disqus comment system
Co-authored-by: echoja <73801151+echoja@users.noreply.github.com>
---
.env.example | 4 +
docs/DISQUS_MIGRATION.md | 127 ++++++++++++++++++
src/app/[locale]/article/[...slug]/layout.tsx | 4 +-
src/app/article/layout.tsx | 4 +-
src/app/en/article/layout.tsx | 4 +-
src/app/ko/article/layout.tsx | 4 +-
src/app/test/layout.tsx | 4 +-
src/common/config.ts | 6 +
src/modules/disqus.tsx | 77 +++++++++++
9 files changed, 224 insertions(+), 10 deletions(-)
create mode 100644 docs/DISQUS_MIGRATION.md
create mode 100644 src/modules/disqus.tsx
diff --git a/.env.example b/.env.example
index 3f393db0..f58c9403 100644
--- a/.env.example
+++ b/.env.example
@@ -5,6 +5,10 @@ NEXT_PUBLIC_GTM_ID=G-xxxxxx
# https://github.com/utterance/utterances
NEXT_PUBLIC_UTTERANCES_REPO=owner/repo
+# Disqus
+# https://disqus.com/
+NEXT_PUBLIC_DISQUS_SHORTNAME=your-disqus-shortname
+
# base url
BASE_URL=http://localhost:3000
NEXT_PUBLIC_BASE_URL=
\ No newline at end of file
diff --git a/docs/DISQUS_MIGRATION.md b/docs/DISQUS_MIGRATION.md
new file mode 100644
index 00000000..4e86c55f
--- /dev/null
+++ b/docs/DISQUS_MIGRATION.md
@@ -0,0 +1,127 @@
+# Utterances에서 Disqus로 마이그레이션 가이드
+
+## 개요
+이 문서는 블로그의 댓글 시스템을 Utterances에서 Disqus로 마이그레이션하는 과정을 설명합니다.
+
+## 1. Disqus 계정 및 사이트 설정
+
+### 1.1 Disqus 계정 생성
+1. [Disqus](https://disqus.com/)에 접속합니다.
+2. 계정이 없다면 새로 생성합니다.
+
+### 1.2 사이트 등록
+1. Disqus에 로그인 후 [Admin](https://disqus.com/admin/) 페이지로 이동합니다.
+2. "Get Started" 또는 "Install Disqus" 를 클릭합니다.
+3. "I want to install Disqus on my site"를 선택합니다.
+4. 사이트 정보를 입력합니다:
+ - **Website Name**: 사이트 이름 (예: "Springfall Blog")
+ - **Disqus URL (shortname)**: 고유한 shortname 입력 (예: "springfall")
+ - **Category**: 블로그 카테고리 선택
+5. 플랜을 선택합니다 (무료 플랜 가능).
+6. 플랫폼 선택 화면에서는 건너뛰고 수동 설정을 진행합니다.
+
+### 1.3 환경 변수 설정
+프로젝트의 `.env` 파일에 Disqus shortname을 추가합니다:
+
+\`\`\`bash
+NEXT_PUBLIC_DISQUS_SHORTNAME=your-disqus-shortname
+\`\`\`
+
+`.env.example` 파일에도 참고용으로 추가되어 있습니다.
+
+## 2. GitHub Issues를 Disqus로 마이그레이션 (수동 작업)
+
+### 2.1 기존 댓글 백업
+1. 기존 Utterances 댓글들은 GitHub Issues에 저장되어 있습니다.
+2. 각 이슈를 열어서 댓글 내용을 확인합니다.
+3. 필요한 경우 중요한 댓글들을 별도로 백업합니다.
+
+### 2.2 Disqus로 댓글 이전
+Utterances의 GitHub Issues를 Disqus로 자동 마이그레이션하는 도구는 없습니다. 다음 방법 중 하나를 선택합니다:
+
+#### 옵션 A: 수동 이전 (권장하지 않음)
+- 중요한 댓글만 선별하여 Disqus에 수동으로 작성합니다.
+- 원본 작성자와 날짜를 명시합니다.
+
+#### 옵션 B: GitHub Issues 유지
+- 기존 Utterances 댓글은 GitHub Issues에 그대로 남겨둡니다.
+- 새로운 댓글만 Disqus에서 받습니다.
+- 필요시 각 포스트에 "이전 댓글은 [GitHub Issues](링크)에서 확인하세요" 안내를 추가합니다.
+
+#### 옵션 C: Disqus Import API 사용 (개발자 전용)
+Disqus는 [Import API](https://help.disqus.com/en/articles/1717131-importing-comments-from-wordpress)를 제공합니다. 다음 단계를 따릅니다:
+
+1. GitHub Issues API를 사용하여 댓글 데이터를 추출합니다.
+2. Disqus의 WXR (WordPress eXtended RSS) 형식으로 변환합니다.
+3. Disqus Admin 페이지에서 데이터를 임포트합니다.
+
+상세 가이드:
+\`\`\`bash
+# GitHub Issues에서 댓글 가져오기
+curl -H "Authorization: token YOUR_GITHUB_TOKEN" \\
+ "https://api.github.com/repos/OWNER/REPO/issues?state=all&labels=utterances"
+
+# 데이터를 WXR 형식으로 변환 (별도 스크립트 필요)
+# Disqus Admin > Discussions > Import에서 업로드
+\`\`\`
+
+## 3. 테마 설정
+
+Disqus는 다크 모드를 기본적으로 지원하지 않습니다. 다음 방법으로 대응합니다:
+
+### 3.1 Disqus 어드민 설정
+1. [Disqus Admin > Settings > General](https://disqus.com/admin/settings/general/)로 이동합니다.
+2. **Color Scheme**을 설정합니다 (Light/Dark/Auto).
+3. 커스텀 CSS로 테마를 조정할 수 있습니다 (Pro 플랜 필요).
+
+### 3.2 코드 레벨 테마 전환
+현재 구현에서는 `useColorMode` 훅을 사용하여 테마 변경을 감지하고 Disqus를 리로드합니다. 하지만 Disqus는 동적 테마 변경을 완벽하게 지원하지 않으므로, 사용자가 페이지를 새로고침해야 할 수 있습니다.
+
+## 4. 테스트
+
+### 4.1 로컬 테스트
+\`\`\`bash
+pnpm dev
+\`\`\`
+
+브라우저에서 블로그 포스트를 열어 Disqus 위젯이 올바르게 로드되는지 확인합니다.
+
+### 4.2 테마 전환 테스트
+- 라이트 모드와 다크 모드를 전환하면서 Disqus가 적절하게 표시되는지 확인합니다.
+- 페이지 새로고침 시 테마가 유지되는지 확인합니다.
+
+### 4.3 프로덕션 테스트
+\`\`\`bash
+pnpm build
+pnpm start
+\`\`\`
+
+프로덕션 빌드에서도 정상 작동하는지 확인합니다.
+
+## 5. 배포
+
+1. 환경 변수가 배포 환경에 설정되어 있는지 확인합니다.
+2. 변경사항을 커밋하고 푸시합니다.
+3. Vercel 또는 다른 호스팅 플랫폼에 배포합니다.
+4. 배포 후 실제 사이트에서 Disqus가 정상 작동하는지 확인합니다.
+
+## 6. 주의사항
+
+- Disqus는 광고를 포함할 수 있습니다 (무료 플랜).
+- Disqus는 사용자 개인정보를 수집합니다. 개인정보 처리방침을 업데이트해야 할 수 있습니다.
+- Disqus는 외부 서비스이므로 페이지 로딩 속도에 영향을 줄 수 있습니다.
+- GitHub Issues에 저장된 기존 댓글은 별도로 관리해야 합니다.
+
+## 7. 롤백
+
+Disqus가 맞지 않는 경우, 다음 단계로 Utterances로 돌아갈 수 있습니다:
+
+1. `src/modules/disqus.tsx` 임포트를 `src/modules/utterances.tsx`로 되돌립니다.
+2. `.env` 파일에서 `NEXT_PUBLIC_DISQUS_SHORTNAME` 대신 `NEXT_PUBLIC_UTTERANCES_REPO`를 설정합니다.
+3. 변경사항을 커밋하고 재배포합니다.
+
+## 추가 리소스
+
+- [Disqus 공식 문서](https://help.disqus.com/)
+- [Disqus Admin Dashboard](https://disqus.com/admin/)
+- [Disqus API 문서](https://disqus.com/api/docs/)
diff --git a/src/app/[locale]/article/[...slug]/layout.tsx b/src/app/[locale]/article/[...slug]/layout.tsx
index 957f63f9..eaaa0d5d 100644
--- a/src/app/[locale]/article/[...slug]/layout.tsx
+++ b/src/app/[locale]/article/[...slug]/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Utterances from "@modules/utterances";
+import Disqus from "@modules/disqus";
import ArticleLayout from "@modules/article/ArticleLayout";
import type { Metadata } from "next";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/article/layout.tsx b/src/app/article/layout.tsx
index 957f63f9..eaaa0d5d 100644
--- a/src/app/article/layout.tsx
+++ b/src/app/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Utterances from "@modules/utterances";
+import Disqus from "@modules/disqus";
import ArticleLayout from "@modules/article/ArticleLayout";
import type { Metadata } from "next";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/en/article/layout.tsx b/src/app/en/article/layout.tsx
index 7e6bf505..f543695b 100644
--- a/src/app/en/article/layout.tsx
+++ b/src/app/en/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Utterances from "@modules/utterances";
+import Disqus from "@modules/disqus";
import type { Metadata } from "next";
import ArticleLayout from "@modules/article/ArticleLayout";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/ko/article/layout.tsx b/src/app/ko/article/layout.tsx
index 7e6bf505..f543695b 100644
--- a/src/app/ko/article/layout.tsx
+++ b/src/app/ko/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Utterances from "@modules/utterances";
+import Disqus from "@modules/disqus";
import type { Metadata } from "next";
import ArticleLayout from "@modules/article/ArticleLayout";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/test/layout.tsx b/src/app/test/layout.tsx
index 699c35c1..52d007a8 100644
--- a/src/app/test/layout.tsx
+++ b/src/app/test/layout.tsx
@@ -1,12 +1,12 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Utterances from "@modules/utterances";
+import Disqus from "@modules/disqus";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
-
+
>
);
}
diff --git a/src/common/config.ts b/src/common/config.ts
index f4749fb0..2d35b629 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -3,3 +3,9 @@ export const DEFAULT_UTTERANCES_REPO = "owner/repo";
export function getUtterancesRepo() {
return process.env.NEXT_PUBLIC_UTTERANCES_REPO || DEFAULT_UTTERANCES_REPO;
}
+
+export const DEFAULT_DISQUS_SHORTNAME = "your-disqus-shortname";
+
+export function getDisqusShortname() {
+ return process.env.NEXT_PUBLIC_DISQUS_SHORTNAME || DEFAULT_DISQUS_SHORTNAME;
+}
diff --git a/src/modules/disqus.tsx b/src/modules/disqus.tsx
new file mode 100644
index 00000000..6572a9bd
--- /dev/null
+++ b/src/modules/disqus.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+
+import { DEFAULT_DISQUS_SHORTNAME, getDisqusShortname } from "@common/config";
+
+import { useColorMode } from "./color-mode/color-mode";
+
+interface IDisqusConfig {
+ page: {
+ url: string;
+ identifier: string;
+ };
+ language?: string;
+}
+
+declare global {
+ interface Window {
+ DISQUS?: {
+ reset: (options: { reload: boolean; config: () => void }) => void;
+ };
+ disqus_config?: (this: IDisqusConfig) => void;
+ }
+}
+
+const Disqus = () => {
+ const { resolved } = useColorMode();
+ const loaded = useRef(false);
+
+ useEffect(() => {
+ const shortname = getDisqusShortname();
+ if (shortname === DEFAULT_DISQUS_SHORTNAME) {
+ return;
+ }
+
+ // Configure Disqus
+ window.disqus_config = function (this: IDisqusConfig) {
+ this.page.url = window.location.href;
+ this.page.identifier = window.location.pathname;
+ };
+
+ if (!loaded.current) {
+ // Load Disqus script for the first time
+ const script = document.createElement("script");
+ script.src = `https://${shortname}.disqus.com/embed.js`;
+ script.setAttribute("data-timestamp", String(+new Date()));
+ script.async = true;
+ (document.head || document.body).appendChild(script);
+ loaded.current = true;
+ }
+ }, []);
+
+ // Reset Disqus when theme changes
+ useEffect(() => {
+ if (loaded.current && window.DISQUS) {
+ window.DISQUS.reset({
+ reload: true,
+ config: function (this: IDisqusConfig) {
+ this.page.url = window.location.href;
+ this.page.identifier = window.location.pathname;
+ // Note: Disqus doesn't natively support dynamic theme switching
+ // Users may need to refresh the page to see theme changes
+ },
+ });
+ }
+ }, [resolved]);
+
+ return (
+
+ );
+};
+
+export default Disqus;
From 3dcf9c1db845902d08a46c6b536b0b570bee5b55 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Dec 2025 23:06:12 +0000
Subject: [PATCH 3/5] Replace Disqus with Giscus for comment system
Co-authored-by: echoja <73801151+echoja@users.noreply.github.com>
---
.env.example | 9 +-
docs/DISQUS_MIGRATION.md | 127 -----------
docs/GISCUS_MIGRATION.md | 197 ++++++++++++++++++
src/app/[locale]/article/[...slug]/layout.tsx | 4 +-
src/app/article/layout.tsx | 4 +-
src/app/en/article/layout.tsx | 4 +-
src/app/ko/article/layout.tsx | 4 +-
src/app/test/layout.tsx | 4 +-
src/common/config.ts | 6 +-
src/modules/disqus.tsx | 77 -------
src/modules/giscus.tsx | 74 +++++++
11 files changed, 290 insertions(+), 220 deletions(-)
delete mode 100644 docs/DISQUS_MIGRATION.md
create mode 100644 docs/GISCUS_MIGRATION.md
delete mode 100644 src/modules/disqus.tsx
create mode 100644 src/modules/giscus.tsx
diff --git a/.env.example b/.env.example
index f58c9403..c72a4676 100644
--- a/.env.example
+++ b/.env.example
@@ -5,9 +5,12 @@ NEXT_PUBLIC_GTM_ID=G-xxxxxx
# https://github.com/utterance/utterances
NEXT_PUBLIC_UTTERANCES_REPO=owner/repo
-# Disqus
-# https://disqus.com/
-NEXT_PUBLIC_DISQUS_SHORTNAME=your-disqus-shortname
+# Giscus - GitHub Discussions based comments
+# https://giscus.app/
+NEXT_PUBLIC_GISCUS_REPO=owner/repo
+NEXT_PUBLIC_GISCUS_REPO_ID=
+NEXT_PUBLIC_GISCUS_CATEGORY=General
+NEXT_PUBLIC_GISCUS_CATEGORY_ID=
# base url
BASE_URL=http://localhost:3000
diff --git a/docs/DISQUS_MIGRATION.md b/docs/DISQUS_MIGRATION.md
deleted file mode 100644
index 4e86c55f..00000000
--- a/docs/DISQUS_MIGRATION.md
+++ /dev/null
@@ -1,127 +0,0 @@
-# Utterances에서 Disqus로 마이그레이션 가이드
-
-## 개요
-이 문서는 블로그의 댓글 시스템을 Utterances에서 Disqus로 마이그레이션하는 과정을 설명합니다.
-
-## 1. Disqus 계정 및 사이트 설정
-
-### 1.1 Disqus 계정 생성
-1. [Disqus](https://disqus.com/)에 접속합니다.
-2. 계정이 없다면 새로 생성합니다.
-
-### 1.2 사이트 등록
-1. Disqus에 로그인 후 [Admin](https://disqus.com/admin/) 페이지로 이동합니다.
-2. "Get Started" 또는 "Install Disqus" 를 클릭합니다.
-3. "I want to install Disqus on my site"를 선택합니다.
-4. 사이트 정보를 입력합니다:
- - **Website Name**: 사이트 이름 (예: "Springfall Blog")
- - **Disqus URL (shortname)**: 고유한 shortname 입력 (예: "springfall")
- - **Category**: 블로그 카테고리 선택
-5. 플랜을 선택합니다 (무료 플랜 가능).
-6. 플랫폼 선택 화면에서는 건너뛰고 수동 설정을 진행합니다.
-
-### 1.3 환경 변수 설정
-프로젝트의 `.env` 파일에 Disqus shortname을 추가합니다:
-
-\`\`\`bash
-NEXT_PUBLIC_DISQUS_SHORTNAME=your-disqus-shortname
-\`\`\`
-
-`.env.example` 파일에도 참고용으로 추가되어 있습니다.
-
-## 2. GitHub Issues를 Disqus로 마이그레이션 (수동 작업)
-
-### 2.1 기존 댓글 백업
-1. 기존 Utterances 댓글들은 GitHub Issues에 저장되어 있습니다.
-2. 각 이슈를 열어서 댓글 내용을 확인합니다.
-3. 필요한 경우 중요한 댓글들을 별도로 백업합니다.
-
-### 2.2 Disqus로 댓글 이전
-Utterances의 GitHub Issues를 Disqus로 자동 마이그레이션하는 도구는 없습니다. 다음 방법 중 하나를 선택합니다:
-
-#### 옵션 A: 수동 이전 (권장하지 않음)
-- 중요한 댓글만 선별하여 Disqus에 수동으로 작성합니다.
-- 원본 작성자와 날짜를 명시합니다.
-
-#### 옵션 B: GitHub Issues 유지
-- 기존 Utterances 댓글은 GitHub Issues에 그대로 남겨둡니다.
-- 새로운 댓글만 Disqus에서 받습니다.
-- 필요시 각 포스트에 "이전 댓글은 [GitHub Issues](링크)에서 확인하세요" 안내를 추가합니다.
-
-#### 옵션 C: Disqus Import API 사용 (개발자 전용)
-Disqus는 [Import API](https://help.disqus.com/en/articles/1717131-importing-comments-from-wordpress)를 제공합니다. 다음 단계를 따릅니다:
-
-1. GitHub Issues API를 사용하여 댓글 데이터를 추출합니다.
-2. Disqus의 WXR (WordPress eXtended RSS) 형식으로 변환합니다.
-3. Disqus Admin 페이지에서 데이터를 임포트합니다.
-
-상세 가이드:
-\`\`\`bash
-# GitHub Issues에서 댓글 가져오기
-curl -H "Authorization: token YOUR_GITHUB_TOKEN" \\
- "https://api.github.com/repos/OWNER/REPO/issues?state=all&labels=utterances"
-
-# 데이터를 WXR 형식으로 변환 (별도 스크립트 필요)
-# Disqus Admin > Discussions > Import에서 업로드
-\`\`\`
-
-## 3. 테마 설정
-
-Disqus는 다크 모드를 기본적으로 지원하지 않습니다. 다음 방법으로 대응합니다:
-
-### 3.1 Disqus 어드민 설정
-1. [Disqus Admin > Settings > General](https://disqus.com/admin/settings/general/)로 이동합니다.
-2. **Color Scheme**을 설정합니다 (Light/Dark/Auto).
-3. 커스텀 CSS로 테마를 조정할 수 있습니다 (Pro 플랜 필요).
-
-### 3.2 코드 레벨 테마 전환
-현재 구현에서는 `useColorMode` 훅을 사용하여 테마 변경을 감지하고 Disqus를 리로드합니다. 하지만 Disqus는 동적 테마 변경을 완벽하게 지원하지 않으므로, 사용자가 페이지를 새로고침해야 할 수 있습니다.
-
-## 4. 테스트
-
-### 4.1 로컬 테스트
-\`\`\`bash
-pnpm dev
-\`\`\`
-
-브라우저에서 블로그 포스트를 열어 Disqus 위젯이 올바르게 로드되는지 확인합니다.
-
-### 4.2 테마 전환 테스트
-- 라이트 모드와 다크 모드를 전환하면서 Disqus가 적절하게 표시되는지 확인합니다.
-- 페이지 새로고침 시 테마가 유지되는지 확인합니다.
-
-### 4.3 프로덕션 테스트
-\`\`\`bash
-pnpm build
-pnpm start
-\`\`\`
-
-프로덕션 빌드에서도 정상 작동하는지 확인합니다.
-
-## 5. 배포
-
-1. 환경 변수가 배포 환경에 설정되어 있는지 확인합니다.
-2. 변경사항을 커밋하고 푸시합니다.
-3. Vercel 또는 다른 호스팅 플랫폼에 배포합니다.
-4. 배포 후 실제 사이트에서 Disqus가 정상 작동하는지 확인합니다.
-
-## 6. 주의사항
-
-- Disqus는 광고를 포함할 수 있습니다 (무료 플랜).
-- Disqus는 사용자 개인정보를 수집합니다. 개인정보 처리방침을 업데이트해야 할 수 있습니다.
-- Disqus는 외부 서비스이므로 페이지 로딩 속도에 영향을 줄 수 있습니다.
-- GitHub Issues에 저장된 기존 댓글은 별도로 관리해야 합니다.
-
-## 7. 롤백
-
-Disqus가 맞지 않는 경우, 다음 단계로 Utterances로 돌아갈 수 있습니다:
-
-1. `src/modules/disqus.tsx` 임포트를 `src/modules/utterances.tsx`로 되돌립니다.
-2. `.env` 파일에서 `NEXT_PUBLIC_DISQUS_SHORTNAME` 대신 `NEXT_PUBLIC_UTTERANCES_REPO`를 설정합니다.
-3. 변경사항을 커밋하고 재배포합니다.
-
-## 추가 리소스
-
-- [Disqus 공식 문서](https://help.disqus.com/)
-- [Disqus Admin Dashboard](https://disqus.com/admin/)
-- [Disqus API 문서](https://disqus.com/api/docs/)
diff --git a/docs/GISCUS_MIGRATION.md b/docs/GISCUS_MIGRATION.md
new file mode 100644
index 00000000..650f0404
--- /dev/null
+++ b/docs/GISCUS_MIGRATION.md
@@ -0,0 +1,197 @@
+# Utterances에서 Giscus로 마이그레이션 가이드
+
+## 개요
+이 문서는 블로그의 댓글 시스템을 Utterances에서 Giscus로 마이그레이션하는 과정을 설명합니다.
+
+Giscus는 Utterances와 유사하지만 GitHub Issues 대신 **GitHub Discussions**를 사용하는 댓글 시스템입니다. Utterances보다 더 나은 기능들을 제공합니다:
+- GitHub Discussions 기반 (Issues 대신)
+- 답글 기능 지원
+- 반응(reactions) 기능
+- 더 나은 다크모드 지원
+- 활발한 유지보수
+
+## 1. GitHub Discussions 활성화
+
+### 1.1 저장소에서 Discussions 활성화
+1. GitHub 저장소로 이동합니다.
+2. **Settings** > **General** > **Features** 섹션으로 이동합니다.
+3. **Discussions** 체크박스를 활성화합니다.
+4. 저장소 상단에 **Discussions** 탭이 나타나는지 확인합니다.
+
+### 1.2 Discussions 카테고리 생성 (선택사항)
+1. 저장소의 **Discussions** 탭으로 이동합니다.
+2. 필요한 경우 댓글 전용 카테고리를 생성합니다 (예: "Comments" 또는 "블로그 댓글").
+3. 카테고리 ID는 나중에 Giscus 설정에 사용됩니다.
+
+## 2. Giscus 설정
+
+### 2.1 Giscus App 설치
+1. [giscus.app](https://giscus.app/)에 접속합니다.
+2. 페이지 하단의 "configuration" 섹션으로 스크롤합니다.
+3. 저장소 이름을 입력합니다 (예: `echoja/springfall`).
+4. Giscus가 저장소를 확인하고 필요한 정보를 표시합니다.
+
+### 2.2 필요한 정보 수집
+Giscus 설정 페이지에서 다음 정보를 확인합니다:
+- **Repository**: 저장소 이름 (예: `echoja/springfall`)
+- **Repository ID**: 저장소의 고유 ID
+- **Category**: Discussion 카테고리 (예: "General" 또는 "Comments")
+- **Category ID**: 카테고리의 고유 ID
+
+### 2.3 환경 변수 설정
+프로젝트의 `.env` 파일에 Giscus 설정을 추가합니다:
+
+\`\`\`bash
+# Giscus 설정
+NEXT_PUBLIC_GISCUS_REPO=owner/repo
+NEXT_PUBLIC_GISCUS_REPO_ID=your-repo-id
+NEXT_PUBLIC_GISCUS_CATEGORY=General
+NEXT_PUBLIC_GISCUS_CATEGORY_ID=your-category-id
+\`\`\`
+
+`.env.example` 파일에도 참고용으로 추가되어 있습니다.
+
+## 3. GitHub Issues를 Discussions로 마이그레이션
+
+### 3.1 기존 Utterances 댓글 확인
+1. 기존 Utterances 댓글들은 GitHub Issues에 저장되어 있습니다.
+2. 저장소의 **Issues** 탭에서 `utterances` 레이블이 붙은 이슈들을 확인합니다.
+
+### 3.2 Issues를 Discussions로 변환
+GitHub에서는 Issues를 Discussions로 변환하는 기능을 제공합니다:
+
+#### GitHub UI를 통한 변환
+1. 각 Issue 페이지로 이동합니다.
+2. 오른쪽 사이드바에서 "Convert to discussion" 버튼을 클릭합니다.
+3. 변환할 Discussion 카테고리를 선택합니다.
+4. "I understand, convert this issue" 버튼을 클릭합니다.
+
+이 작업은 각 이슈마다 수동으로 해야 합니다.
+
+#### GitHub CLI를 통한 일괄 변환 (선택사항)
+GitHub CLI를 사용하여 여러 이슈를 한 번에 변환할 수 있습니다:
+
+\`\`\`bash
+# utterances 레이블이 있는 모든 이슈 목록 가져오기
+gh issue list --label utterances --json number,title --jq '.[] | [.number, .title] | @tsv'
+
+# 각 이슈를 Discussion으로 변환 (수동으로 번호 입력 필요)
+gh issue transfer --target-repo-id
+\`\`\`
+
+**참고**: GitHub API를 통한 자동 변환 스크립트를 작성할 수도 있지만, 수동 확인을 권장합니다.
+
+### 3.3 마이그레이션 전략
+
+다음 옵션 중 하나를 선택합니다:
+
+#### 옵션 A: 모든 댓글 변환 (권장)
+- 모든 Utterances 이슈를 Discussions로 변환합니다.
+- 기존 댓글들이 그대로 유지되며 URL도 자동으로 리디렉션됩니다.
+- Giscus가 자동으로 기존 Discussion을 찾아 표시합니다.
+
+#### 옵션 B: 선택적 변환
+- 중요한 댓글이 있는 이슈만 변환합니다.
+- 나머지는 Issues에 그대로 두고 새 댓글만 Giscus로 받습니다.
+
+#### 옵션 C: 새로 시작
+- 기존 댓글은 Issues에 보관하고 새 댓글만 Discussions에서 받습니다.
+- 필요시 각 글에 "이전 댓글은 [GitHub Issues](링크)에서 확인하세요" 안내 추가.
+
+## 4. Giscus 동작 방식
+
+Giscus는 다음과 같이 작동합니다:
+1. 사용자가 블로그 글을 방문하면 Giscus가 로드됩니다.
+2. Giscus는 페이지의 `pathname`을 기준으로 Discussion을 찾습니다.
+3. 해당 Discussion이 없으면 첫 댓글 작성 시 자동으로 생성됩니다.
+4. 기존 Utterances Issues를 Discussions로 변환한 경우, Giscus가 자동으로 연결합니다.
+
+## 5. 테마 설정
+
+### 5.1 다크모드 지원
+Giscus는 Utterances보다 더 나은 다크모드 지원을 제공합니다:
+- 현재 구현에서는 `useColorMode` 훅을 사용하여 테마를 감지합니다.
+- 테마 변경 시 Giscus에 메시지를 전송하여 실시간으로 테마를 변경합니다.
+- 페이지 새로고침 없이 테마가 즉시 변경됩니다.
+
+### 5.2 사용 가능한 테마
+Giscus는 다양한 테마를 지원합니다:
+- `light` - 기본 라이트 테마
+- `dark` - 기본 다크 테마
+- `preferred_color_scheme` - 시스템 설정 따름
+- GitHub 테마들 (`github_light`, `github_dark` 등)
+
+현재 구현은 `light`와 `dark`를 사용합니다.
+
+## 6. 테스트
+
+### 6.1 로컬 테스트
+\`\`\`bash
+pnpm dev
+\`\`\`
+
+브라우저에서 블로그 포스트를 열어 Giscus 위젯이 올바르게 로드되는지 확인합니다.
+
+### 6.2 테마 전환 테스트
+- 라이트 모드와 다크 모드를 전환하면서 Giscus가 즉시 반응하는지 확인합니다.
+- Utterances와 달리 페이지 새로고침 없이 테마가 변경되어야 합니다.
+
+### 6.3 댓글 작성 테스트
+1. GitHub 계정으로 로그인합니다.
+2. 테스트 댓글을 작성합니다.
+3. 저장소의 Discussions에서 새 Discussion이 생성되었는지 확인합니다.
+
+### 6.4 프로덕션 테스트
+\`\`\`bash
+pnpm build
+pnpm start
+\`\`\`
+
+프로덕션 빌드에서도 정상 작동하는지 확인합니다.
+
+## 7. 배포
+
+1. 환경 변수가 배포 환경(Vercel 등)에 설정되어 있는지 확인합니다:
+ - `NEXT_PUBLIC_GISCUS_REPO`
+ - `NEXT_PUBLIC_GISCUS_REPO_ID`
+ - `NEXT_PUBLIC_GISCUS_CATEGORY`
+ - `NEXT_PUBLIC_GISCUS_CATEGORY_ID`
+2. 변경사항을 커밋하고 푸시합니다.
+3. 배포 플랫폼에서 자동으로 빌드 및 배포됩니다.
+4. 배포 후 실제 사이트에서 Giscus가 정상 작동하는지 확인합니다.
+
+## 8. Utterances vs Giscus 비교
+
+| 기능 | Utterances | Giscus |
+|------|-----------|--------|
+| 기반 | GitHub Issues | GitHub Discussions |
+| 답글 | ❌ | ✅ |
+| 반응(Reactions) | 제한적 | ✅ 완전 지원 |
+| 다크모드 | 페이지 새로고침 필요 | 실시간 변경 |
+| 유지보수 | 중단됨 | 활발함 |
+| 댓글 정렬 | 제한적 | 다양한 옵션 |
+| 카테고리 | ❌ | ✅ |
+
+## 9. 주의사항
+
+- Giscus를 사용하려면 저장소가 **public**이어야 합니다.
+- 댓글 작성자는 GitHub 계정이 필요합니다.
+- GitHub Discussions가 활성화되어 있어야 합니다.
+- 기존 Issues를 Discussions로 변환하는 작업은 되돌릴 수 없으니 주의하세요.
+
+## 10. 롤백
+
+Giscus가 맞지 않는 경우, 다음 단계로 Utterances로 돌아갈 수 있습니다:
+
+1. `src/modules/giscus.tsx` 임포트를 `src/modules/utterances.tsx`로 되돌립니다.
+2. `.env` 파일에서 Giscus 관련 변수를 제거하고 `NEXT_PUBLIC_UTTERANCES_REPO`를 설정합니다.
+3. 변경사항을 커밋하고 재배포합니다.
+
+**참고**: Discussions로 변환한 댓글은 Issues로 되돌릴 수 없으므로, 마이그레이션 전 백업을 권장합니다.
+
+## 11. 추가 리소스
+
+- [Giscus 공식 사이트](https://giscus.app/)
+- [Giscus GitHub 저장소](https://github.com/giscus/giscus)
+- [GitHub Discussions 문서](https://docs.github.com/en/discussions)
+- [Converting an issue to a discussion](https://docs.github.com/en/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion)
diff --git a/src/app/[locale]/article/[...slug]/layout.tsx b/src/app/[locale]/article/[...slug]/layout.tsx
index eaaa0d5d..26ae43db 100644
--- a/src/app/[locale]/article/[...slug]/layout.tsx
+++ b/src/app/[locale]/article/[...slug]/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Disqus from "@modules/disqus";
+import Giscus from "@modules/giscus";
import ArticleLayout from "@modules/article/ArticleLayout";
import type { Metadata } from "next";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/article/layout.tsx b/src/app/article/layout.tsx
index eaaa0d5d..26ae43db 100644
--- a/src/app/article/layout.tsx
+++ b/src/app/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Disqus from "@modules/disqus";
+import Giscus from "@modules/giscus";
import ArticleLayout from "@modules/article/ArticleLayout";
import type { Metadata } from "next";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/en/article/layout.tsx b/src/app/en/article/layout.tsx
index f543695b..c2ccfeee 100644
--- a/src/app/en/article/layout.tsx
+++ b/src/app/en/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Disqus from "@modules/disqus";
+import Giscus from "@modules/giscus";
import type { Metadata } from "next";
import ArticleLayout from "@modules/article/ArticleLayout";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/ko/article/layout.tsx b/src/app/ko/article/layout.tsx
index f543695b..c2ccfeee 100644
--- a/src/app/ko/article/layout.tsx
+++ b/src/app/ko/article/layout.tsx
@@ -1,5 +1,5 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Disqus from "@modules/disqus";
+import Giscus from "@modules/giscus";
import type { Metadata } from "next";
import ArticleLayout from "@modules/article/ArticleLayout";
@@ -14,7 +14,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<>
{children}
-
+
>
);
}
diff --git a/src/app/test/layout.tsx b/src/app/test/layout.tsx
index 52d007a8..d3a9dc0e 100644
--- a/src/app/test/layout.tsx
+++ b/src/app/test/layout.tsx
@@ -1,12 +1,12 @@
import ArticlePageHeader from "@modules/layout/ArticlePageHeader";
-import Disqus from "@modules/disqus";
+import Giscus from "@modules/giscus";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
-
+
>
);
}
diff --git a/src/common/config.ts b/src/common/config.ts
index 2d35b629..9adbc5d1 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -4,8 +4,8 @@ export function getUtterancesRepo() {
return process.env.NEXT_PUBLIC_UTTERANCES_REPO || DEFAULT_UTTERANCES_REPO;
}
-export const DEFAULT_DISQUS_SHORTNAME = "your-disqus-shortname";
+export const DEFAULT_GISCUS_REPO = "owner/repo";
-export function getDisqusShortname() {
- return process.env.NEXT_PUBLIC_DISQUS_SHORTNAME || DEFAULT_DISQUS_SHORTNAME;
+export function getGiscusRepo() {
+ return process.env.NEXT_PUBLIC_GISCUS_REPO || DEFAULT_GISCUS_REPO;
}
diff --git a/src/modules/disqus.tsx b/src/modules/disqus.tsx
deleted file mode 100644
index 6572a9bd..00000000
--- a/src/modules/disqus.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client";
-
-import { useEffect, useRef } from "react";
-
-import { DEFAULT_DISQUS_SHORTNAME, getDisqusShortname } from "@common/config";
-
-import { useColorMode } from "./color-mode/color-mode";
-
-interface IDisqusConfig {
- page: {
- url: string;
- identifier: string;
- };
- language?: string;
-}
-
-declare global {
- interface Window {
- DISQUS?: {
- reset: (options: { reload: boolean; config: () => void }) => void;
- };
- disqus_config?: (this: IDisqusConfig) => void;
- }
-}
-
-const Disqus = () => {
- const { resolved } = useColorMode();
- const loaded = useRef(false);
-
- useEffect(() => {
- const shortname = getDisqusShortname();
- if (shortname === DEFAULT_DISQUS_SHORTNAME) {
- return;
- }
-
- // Configure Disqus
- window.disqus_config = function (this: IDisqusConfig) {
- this.page.url = window.location.href;
- this.page.identifier = window.location.pathname;
- };
-
- if (!loaded.current) {
- // Load Disqus script for the first time
- const script = document.createElement("script");
- script.src = `https://${shortname}.disqus.com/embed.js`;
- script.setAttribute("data-timestamp", String(+new Date()));
- script.async = true;
- (document.head || document.body).appendChild(script);
- loaded.current = true;
- }
- }, []);
-
- // Reset Disqus when theme changes
- useEffect(() => {
- if (loaded.current && window.DISQUS) {
- window.DISQUS.reset({
- reload: true,
- config: function (this: IDisqusConfig) {
- this.page.url = window.location.href;
- this.page.identifier = window.location.pathname;
- // Note: Disqus doesn't natively support dynamic theme switching
- // Users may need to refresh the page to see theme changes
- },
- });
- }
- }, [resolved]);
-
- return (
-
- );
-};
-
-export default Disqus;
diff --git a/src/modules/giscus.tsx b/src/modules/giscus.tsx
new file mode 100644
index 00000000..ccaeab9a
--- /dev/null
+++ b/src/modules/giscus.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+
+import { DEFAULT_GISCUS_REPO, getGiscusRepo } from "@common/config";
+
+import { useColorMode } from "./color-mode/color-mode";
+
+const Giscus = () => {
+ const { resolved } = useColorMode();
+ const loaded = useRef(false);
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ const repo = getGiscusRepo();
+ if (repo === DEFAULT_GISCUS_REPO) {
+ return;
+ }
+ if (!containerRef.current) {
+ return;
+ }
+ if (loaded.current) {
+ return;
+ }
+
+ // Create and inject the giscus script
+ const script = document.createElement("script");
+ script.src = "https://giscus.app/client.js";
+ script.async = true;
+ script.crossOrigin = "anonymous";
+ script.setAttribute("data-repo", repo);
+ script.setAttribute("data-repo-id", process.env.NEXT_PUBLIC_GISCUS_REPO_ID || "");
+ script.setAttribute("data-category", process.env.NEXT_PUBLIC_GISCUS_CATEGORY || "General");
+ script.setAttribute("data-category-id", process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || "");
+ script.setAttribute("data-mapping", "pathname");
+ script.setAttribute("data-strict", "0");
+ script.setAttribute("data-reactions-enabled", "1");
+ script.setAttribute("data-emit-metadata", "0");
+ script.setAttribute("data-input-position", "bottom");
+ script.setAttribute("data-theme", resolved === "dark" ? "dark" : "light");
+ script.setAttribute("data-lang", "ko");
+ script.setAttribute("data-loading", "lazy");
+
+ containerRef.current.appendChild(script);
+ loaded.current = true;
+ }, []);
+
+ // Update theme when color mode changes
+ useEffect(() => {
+ if (!loaded.current) {
+ return;
+ }
+
+ const iframe = document.querySelector(
+ "iframe.giscus-frame",
+ );
+ if (iframe?.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ giscus: {
+ setConfig: {
+ theme: resolved === "dark" ? "dark" : "light",
+ },
+ },
+ },
+ "https://giscus.app",
+ );
+ }
+ }, [resolved]);
+
+ return ;
+};
+
+export default Giscus;
From c36e1ec0bfb7dcff9c0424674654b3e27ce147df Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Dec 2025 23:15:09 +0000
Subject: [PATCH 4/5] Remove Utterances and defensive code, simplify Giscus
config
Co-authored-by: echoja <73801151+echoja@users.noreply.github.com>
---
.env.example | 6 +--
docs/GISCUS_MIGRATION.md | 14 +------
src/common/config.ts | 10 +----
src/modules/giscus.tsx | 11 +-----
src/modules/utterances.tsx | 80 --------------------------------------
5 files changed, 5 insertions(+), 116 deletions(-)
delete mode 100644 src/modules/utterances.tsx
diff --git a/.env.example b/.env.example
index c72a4676..e7fcb721 100644
--- a/.env.example
+++ b/.env.example
@@ -1,15 +1,11 @@
# Google Tag Manager
NEXT_PUBLIC_GTM_ID=G-xxxxxx
-# utterances
-# https://github.com/utterance/utterances
-NEXT_PUBLIC_UTTERANCES_REPO=owner/repo
-
# Giscus - GitHub Discussions based comments
# https://giscus.app/
+# Get these values from https://giscus.app/ after configuring your repository
NEXT_PUBLIC_GISCUS_REPO=owner/repo
NEXT_PUBLIC_GISCUS_REPO_ID=
-NEXT_PUBLIC_GISCUS_CATEGORY=General
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
# base url
diff --git a/docs/GISCUS_MIGRATION.md b/docs/GISCUS_MIGRATION.md
index 650f0404..7265b966 100644
--- a/docs/GISCUS_MIGRATION.md
+++ b/docs/GISCUS_MIGRATION.md
@@ -35,7 +35,6 @@ Giscus는 Utterances와 유사하지만 GitHub Issues 대신 **GitHub Discussion
Giscus 설정 페이지에서 다음 정보를 확인합니다:
- **Repository**: 저장소 이름 (예: `echoja/springfall`)
- **Repository ID**: 저장소의 고유 ID
-- **Category**: Discussion 카테고리 (예: "General" 또는 "Comments")
- **Category ID**: 카테고리의 고유 ID
### 2.3 환경 변수 설정
@@ -45,7 +44,6 @@ Giscus 설정 페이지에서 다음 정보를 확인합니다:
# Giscus 설정
NEXT_PUBLIC_GISCUS_REPO=owner/repo
NEXT_PUBLIC_GISCUS_REPO_ID=your-repo-id
-NEXT_PUBLIC_GISCUS_CATEGORY=General
NEXT_PUBLIC_GISCUS_CATEGORY_ID=your-category-id
\`\`\`
@@ -172,23 +170,13 @@ pnpm start
| 댓글 정렬 | 제한적 | 다양한 옵션 |
| 카테고리 | ❌ | ✅ |
-## 9. 주의사항
+## 10. 주의사항
- Giscus를 사용하려면 저장소가 **public**이어야 합니다.
- 댓글 작성자는 GitHub 계정이 필요합니다.
- GitHub Discussions가 활성화되어 있어야 합니다.
- 기존 Issues를 Discussions로 변환하는 작업은 되돌릴 수 없으니 주의하세요.
-## 10. 롤백
-
-Giscus가 맞지 않는 경우, 다음 단계로 Utterances로 돌아갈 수 있습니다:
-
-1. `src/modules/giscus.tsx` 임포트를 `src/modules/utterances.tsx`로 되돌립니다.
-2. `.env` 파일에서 Giscus 관련 변수를 제거하고 `NEXT_PUBLIC_UTTERANCES_REPO`를 설정합니다.
-3. 변경사항을 커밋하고 재배포합니다.
-
-**참고**: Discussions로 변환한 댓글은 Issues로 되돌릴 수 없으므로, 마이그레이션 전 백업을 권장합니다.
-
## 11. 추가 리소스
- [Giscus 공식 사이트](https://giscus.app/)
diff --git a/src/common/config.ts b/src/common/config.ts
index 9adbc5d1..65609923 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,11 +1,3 @@
-export const DEFAULT_UTTERANCES_REPO = "owner/repo";
-
-export function getUtterancesRepo() {
- return process.env.NEXT_PUBLIC_UTTERANCES_REPO || DEFAULT_UTTERANCES_REPO;
-}
-
-export const DEFAULT_GISCUS_REPO = "owner/repo";
-
export function getGiscusRepo() {
- return process.env.NEXT_PUBLIC_GISCUS_REPO || DEFAULT_GISCUS_REPO;
+ return process.env.NEXT_PUBLIC_GISCUS_REPO;
}
diff --git a/src/modules/giscus.tsx b/src/modules/giscus.tsx
index ccaeab9a..4808260d 100644
--- a/src/modules/giscus.tsx
+++ b/src/modules/giscus.tsx
@@ -2,7 +2,7 @@
import { useEffect, useRef } from "react";
-import { DEFAULT_GISCUS_REPO, getGiscusRepo } from "@common/config";
+import { getGiscusRepo } from "@common/config";
import { useColorMode } from "./color-mode/color-mode";
@@ -13,13 +13,7 @@ const Giscus = () => {
useEffect(() => {
const repo = getGiscusRepo();
- if (repo === DEFAULT_GISCUS_REPO) {
- return;
- }
- if (!containerRef.current) {
- return;
- }
- if (loaded.current) {
+ if (!repo || !containerRef.current || loaded.current) {
return;
}
@@ -30,7 +24,6 @@ const Giscus = () => {
script.crossOrigin = "anonymous";
script.setAttribute("data-repo", repo);
script.setAttribute("data-repo-id", process.env.NEXT_PUBLIC_GISCUS_REPO_ID || "");
- script.setAttribute("data-category", process.env.NEXT_PUBLIC_GISCUS_CATEGORY || "General");
script.setAttribute("data-category-id", process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || "");
script.setAttribute("data-mapping", "pathname");
script.setAttribute("data-strict", "0");
diff --git a/src/modules/utterances.tsx b/src/modules/utterances.tsx
deleted file mode 100644
index 7e61e6c0..00000000
--- a/src/modules/utterances.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-"use client";
-
-import { useEffect, useRef, useState } from "react";
-
-import { DEFAULT_UTTERANCES_REPO, getUtterancesRepo } from "@common/config";
-
-import { useColorMode } from "./color-mode/color-mode";
-
-const Utterances = () => {
- const { resolved } = useColorMode();
- const [lightRef, setLightRef] = useState(null);
- const [darkRef, setDarkRef] = useState(null);
- const loaded = useRef(false);
-
- // Inject both iframes once
- useEffect(() => {
- const repo = getUtterancesRepo();
- if (loaded.current) {
- return;
- }
- if (!lightRef || !darkRef) {
- return;
- }
- if (repo === DEFAULT_UTTERANCES_REPO) {
- return;
- }
-
- const add = (
- container: HTMLDivElement,
- theme: "github-light" | "github-dark",
- ) => {
- const script = document.createElement("script");
- script.src = "https://utteranc.es/client.js";
- script.async = true;
- script.crossOrigin = "anonymous";
- script.setAttribute("repo", repo);
- script.setAttribute("issue-term", "pathname");
- script.setAttribute("theme", theme);
- container.appendChild(script);
- return script;
- };
-
- const sLight = add(lightRef, "github-light");
- const sDark = add(darkRef, "github-dark");
- loaded.current = true;
-
- return () => {
- sLight.remove();
- sDark.remove();
- [lightRef, darkRef].forEach((c) => {
- if (!c) {
- return;
- }
- while (c.firstChild) {
- c.removeChild(c.firstChild);
- }
- });
- loaded.current = false;
- };
- }, [lightRef, darkRef]);
-
- const isDark = resolved === "dark";
-
- return (
- <>
-
-
- >
- );
-};
-
-export default Utterances;
From 64ea41b80da68f9f9dbcd3a8d17261d3d2745a3e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Dec 2025 23:19:25 +0000
Subject: [PATCH 5/5] Fix TypeScript error in HtmlLangUpdater: handle null
locale
Co-authored-by: echoja <73801151+echoja@users.noreply.github.com>
---
src/modules/i18n/HtmlLangUpdater.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/modules/i18n/HtmlLangUpdater.tsx b/src/modules/i18n/HtmlLangUpdater.tsx
index 9d3dc94c..eeb085e7 100644
--- a/src/modules/i18n/HtmlLangUpdater.tsx
+++ b/src/modules/i18n/HtmlLangUpdater.tsx
@@ -3,13 +3,14 @@
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { getLocaleFromPathname } from "./util";
+import { i18n } from "./types";
const HtmlLangUpdater = () => {
const pathname = usePathname();
useEffect(() => {
const locale = getLocaleFromPathname(pathname);
- document.documentElement.lang = locale;
+ document.documentElement.lang = locale ?? i18n.defaultLocale;
}, [pathname]);
return null;