From 8fda7d31f9d2bb6a530ae30a79c80e69642b6618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B0=EC=83=81=EB=B9=88?= Date: Sun, 16 Aug 2026 20:41:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(markdown):=20KaTeX=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EC=8B=9C=ED=8A=B8=20=EB=A1=9C=EB=93=9C=20=EC=8B=9C=20?= =?UTF-8?q?NEXT=5FPUBLIC=5FBASE=5FPATH=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MarkdownContent에서 KaTeX CSS 링크 삽입 시 NEXT_PUBLIC_BASE_PATH 환경변수를 반영하도록 getKatexStylesheetUrl 함수 적용 - GitHub Pages 서브디렉터리 배포 환경에서 KaTeX 스타일시트 404 방지 - getKatexStylesheetUrl에 대한 단위 테스트(tests/katex-assets.test.ts) 추가 --- src/components/markdown-content.tsx | 18 +++++++++++++++++- tests/katex-assets.test.ts | 27 ++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/components/markdown-content.tsx b/src/components/markdown-content.tsx index c5af425..773098b 100644 --- a/src/components/markdown-content.tsx +++ b/src/components/markdown-content.tsx @@ -9,6 +9,20 @@ type MarkdownContentProps = Readonly<{ html: SanitizedHtml }> +const DEFAULT_KATEX_CSS_PATH = "/katex/katex.min.css" + +/** + * GitHub Pages 서브경로(e.g. /repo/) 배포 환경을 지원하기 위해 + * 환경변수 NEXT_PUBLIC_BASE_PATH 가 지정된 경우 KaTeX 스타일시트 경로 앞에 접두사로 조합합니다. + */ +export function getKatexStylesheetUrl(): string { + // biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access + const basePath = process.env["NEXT_PUBLIC_BASE_PATH"]?.trim() ?? "" + const normalizedBasePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath + + return `${normalizedBasePath}${DEFAULT_KATEX_CSS_PATH}` +} + const markdownContentClassName = cn( "prose prose-neutral max-w-none text-foreground dark:prose-invert", "prose-a:text-foreground prose-a:decoration-border prose-a:underline-offset-4", @@ -30,7 +44,9 @@ export function MarkdownContent({ contentKey, hasDiagram, hasMath, html }: Markd return ( <> - {hasMath ? : null} + {hasMath ? ( + + ) : null}
{ + const originalEnv = process.env + + beforeEach(() => { + process.env = { ...originalEnv } + }) + + afterEach(() => { + process.env = originalEnv + }) + it("Given repository-owned KaTeX assets When checking the public directory Then includes CSS and its license", () => { expect(existsSync(stylesheetPath)).toBe(true) expect(existsSync(licensePath)).toBe(true) @@ -34,4 +45,18 @@ describe("local KaTeX assets", () => { expect(packageJson).not.toMatch(/^\s*"katex":/m) expect(packageJson).not.toContain("copy-katex-assets") }) + + describe("getKatexStylesheetUrl", () => { + it("Given no NEXT_PUBLIC_BASE_PATH When resolving Then returns root stylesheet path", () => { + // biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access + delete process.env["NEXT_PUBLIC_BASE_PATH"] + expect(getKatexStylesheetUrl()).toBe("/katex/katex.min.css") + }) + + it("Given NEXT_PUBLIC_BASE_PATH When resolving Then prepends base path with normalized slashes", () => { + // biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access + process.env["NEXT_PUBLIC_BASE_PATH"] = "/my-blog/" + expect(getKatexStylesheetUrl()).toBe("/my-blog/katex/katex.min.css") + }) + }) })