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'], }, }, },