diff --git a/apps/blog/src/app/layout.tsx b/apps/blog/src/app/layout.tsx
index 01ec5a8..6b3e593 100644
--- a/apps/blog/src/app/layout.tsx
+++ b/apps/blog/src/app/layout.tsx
@@ -1,23 +1,26 @@
import { PropsWithChildren } from 'react';
import './global.css';
-// import { Noto_Sans_KR } from 'next/font/google';
+import { Noto_Sans_KR } from 'next/font/google';
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
import { Constants } from '@libs/constants';
-// Noto Sans KR을 서브셋으로 최적화
-// - 한글 기본 2,350자만 포함 (전체 11,172자 대신)
-// - 폰트 크기: 76KB → ~20KB로 감소
-// - swap: 폰트 로딩 중에도 텍스트 표시
-// const font = Noto_Sans_KR({
-// weight: ['400', '700'], // 필요한 weight만 로드
-// subsets: ['latin'],
-// display: 'swap', // 폰트 로딩 중에도 텍스트 즉시 표시
-// variable: '--font-noto-sans-kr',
-// preload: true,
-// fallback: ['system-ui', '-apple-system', 'sans-serif'],
-// adjustFontFallback: true, // CLS 방지
-// });
+// OS별 한글 렌더링 편차를 없애기 위해 활성화(#95). next/font는 빌드타임에 셀프호스트하므로
+// 런타임에 fonts.googleapis.com으로 나가는 요청(preconnect 포함)이 필요 없다.
+// subsets는 프리로드 대상(latin woff2)만 지정한다 — next/font의 Noto_Sans_KR에는 'korean'
+// 서브셋이 없어, 한글 글리프는 unicode-range 슬라이스(다수의 woff2)로 필요한 만큼 온디맨드
+// 로드된다. 따라서 첫 방문 시 한글은 폴백→스왑(FOUT)이 발생하며, 아래 display/adjustFontFallback
+// 조합으로 로딩 중 표시와 스왑 시 CLS를 완화한다.
+const font = Noto_Sans_KR({
+ // 실사용 weight만 로드: 400(본문)·500(font-medium 강조)·700(헤딩/볼드).
+ // 600은 th(post-detail.module.scss) 한 곳뿐이라 로드하지 않고 700 상향 매칭을 감수한다.
+ weight: ['400', '500', '700'],
+ subsets: ['latin'],
+ display: 'swap', // 폰트 로딩 중에도 텍스트 즉시 표시
+ preload: true,
+ fallback: ['system-ui', '-apple-system', 'sans-serif'],
+ adjustFontFallback: true, // CLS 방지
+});
const { siteConfig, openGraph } = Constants;
@@ -86,15 +89,14 @@ export default async function RootLayout({ children }: PropsWithChildren) {
}}
/>
- {/* */}
- {/* */}
-
+ {/* font.className이 셀프호스트된 Noto Sans KR과 조정된 폴백(fallback)을 body 전체에 적용한다. */}
+
{children}
diff --git a/apps/blog/src/components/post-detail/post-detail.module.scss b/apps/blog/src/components/post-detail/post-detail.module.scss
index dce9bd1..ec14408 100644
--- a/apps/blog/src/components/post-detail/post-detail.module.scss
+++ b/apps/blog/src/components/post-detail/post-detail.module.scss
@@ -1,4 +1,12 @@
.postDetail {
+ // === 타입 스케일 토큰 (#95) ===
+ // 흩어진 px 하드코딩을 토큰으로 모아 h1 > h2 > h3 > 본문 위계가 항상 유지되게 한다.
+ // 모바일 값은 하단 미디어쿼리에서 같은 토큰만 덮어쓴다.
+ --post-h1-size: 32px;
+ --post-h2-size: 24px;
+ --post-h3-size: 20px; // 본문(16px)과 같아 위계가 없던 16px에서 한 단계 상향
+ --post-body-size: 16px;
+
h1,
h2,
h3 {
@@ -7,27 +15,33 @@
}
h1 {
- font-size: 32px;
+ font-size: var(--post-h1-size);
}
h2 {
- font-size: 24px;
+ font-size: var(--post-h2-size);
}
h3 {
- font-size: 16px;
+ font-size: var(--post-h3-size);
}
p {
padding-top: 16px;
padding-bottom: 24px;
- line-height: 28px;
+ // 한글 본문 가독성을 위해 28px(비율 1.75)에서 1.8로 상향(#95).
+ // unitless 비율이라 모바일 본문 14px에서도 같은 비율(약 25.2px)로 스케일되어
+ // #118에서 복원한 모바일 줄간격(25px, 약 1.79)의 의도를 그대로 계승한다.
+ line-height: 1.8;
+ font-size: var(--post-body-size);
word-break: keep-all;
}
ul,
ol {
padding: 5px 0;
+ // 본문 크기의 단일 출처를 위해 리스트도 토큰을 참조한다(기본값 16px과 동일해 시각 변화 없음).
+ font-size: var(--post-body-size);
}
li {
@@ -97,6 +111,8 @@
width: 100%;
margin: 16px 0;
border-collapse: collapse;
+ // 표 본문도 본문 크기 토큰을 따른다(기본값 16px과 동일해 시각 변화 없음).
+ font-size: var(--post-body-size);
}
th,
@@ -112,6 +128,13 @@
}
@media only screen and (max-width: 800px) {
+ // 모바일 타입 스케일(#95): 기존 h2 16px·h3 14px가 본문 14px과 겹쳐 위계가 사라졌던 것을,
+ // 본문 14px 기준으로 h3까지 최소 한 단계(2px 이상) 차이가 유지되도록 토큰만 덮어쓴다.
+ --post-h1-size: 24px;
+ --post-h2-size: 20px;
+ --post-h3-size: 16px;
+ --post-body-size: 14px;
+
h1,
h2,
h3 {
@@ -119,32 +142,17 @@
padding: 10px 0;
}
- h1 {
- font-size: 24px;
- }
-
- h2 {
- font-size: 16px;
- }
-
- h3 {
- font-size: 14px;
- }
-
p {
padding-top: 8px;
padding-bottom: 12px;
- // 본문 14px 기준 가독 line-height. 폰트가 12→14px로 커졌는데 22px에 머물러
- // 줄간격 비율이 1.57로 좁아졌던 것을, 데스크톱 비율(28px/16px≈1.75)에 근접하게 복원한다(25/14≈1.79).
- line-height: 25px;
- font-size: 14px; // 모바일 가독성을 위해 12px에서 14px로 상향
- word-break: keep-all;
+ // 폰트 크기는 --post-body-size(14px) 토큰이, 줄간격은 상단의 unitless 1.8이 담당한다.
+ // (#118에서 복원한 25px≈1.79 비율은 1.8 비율 상속으로 그대로 유지된다)
}
ul,
ol {
padding: 0;
- font-size: 14px; // 본문 폰트와 통일해 12px에서 14px로 상향
+ // font-size는 기본 스코프의 토큰 선언이 모바일 토큰 값(14px)으로 재해석되므로 중복 선언하지 않는다.
}
li {
@@ -191,10 +199,7 @@
}
}
- table {
- font-size: 14px; // 본문 폰트와 통일해 12px에서 14px로 상향
- }
-
+ // table의 font-size는 기본 스코프의 토큰 선언이 모바일 토큰 값(14px)으로 재해석되므로 중복 선언하지 않는다.
th,
td {
padding: 6px 8px;
diff --git a/apps/blog/tailwind.config.js b/apps/blog/tailwind.config.js
index a54b123..710706c 100644
--- a/apps/blog/tailwind.config.js
+++ b/apps/blog/tailwind.config.js
@@ -10,7 +10,6 @@ export default {
extend: {
fontFamily: {
sans: ['system-ui', 'sans-serif'],
- // sans: ['var(--font-noto-sans-kr)', 'system-ui', 'sans-serif'],
},
},
},