Skip to content

posting: fout현상해결도전기 - #75

Merged
youngduck merged 2 commits into
mainfrom
posting/260507
May 7, 2026
Merged

posting: fout현상해결도전기#75
youngduck merged 2 commits into
mainfrom
posting/260507

Conversation

@youngduck

Copy link
Copy Markdown
Owner

PR전 코드 퀄리티 체크하기

작업내용

작업내용을 간단/상세하게 작성해주세요.

  1. fout에 대한 게시글을 추가했습니다.

🔍 가독성 (Readability) CHECK

명명 규칙

  • 매직넘버를 명명된 상수로 교체했나요?
    • const ANIMATION_DELAY_MS = 300 형태로 의미 있는 이름 사용
  • 복잡한 조건문을 명명된 변수로 분리했나요?
    • const isValidUser = user.age >= 18 && user.isVerified
  • 변수명이 그 자체로 의미를 전달하나요?
    • userDataauthenticatedUser, listactiveUserList

구조 및 구성

  • import문이 절대경로로 정리/정렬되어 있나요?
  • 변수 선언이 컴포넌트 상단에서 이루어지고 하단에서 사용되나요?
  • 위에서 아래로 읽기 쉬운 흐름으로 구성되어 있나요?

추상화 및 분리

  • 복잡한 로직을 전용 컴포넌트/훅으로 추상화했나요?
    • 인증 체크 → AuthGuard 컴포넌트
    • 복잡한 상호작용 → 전용 버튼 컴포넌트
  • 조건부 렌더링이 복잡한 경우 별도 컴포넌트로 분리했나요?
    • ViewerSubmitButton, AdminSubmitButton로 역할별 분리
  • 복잡한 삼항 연산자를 if/else 또는 IIFE로 교체했나요?

🎯 예측 가능성 (Predictability) CHECK

반환 타입 일관성

  • 유사한 기능의 함수/훅이 일관된 반환 타입을 사용하나요?
    • API 훅: UseQueryResult<T, Error> 일관 사용
    • 검증 함수: { ok: boolean; reason?: string } 형태 일관 사용

단일 책임 원칙

  • 함수가 이름에서 암시하는 동작만 수행하나요?
    • fetchBalance()가 로깅 등 부수효과 없이 balance만 반환
  • 숨겨진 사이드 이펙트가 없나요?

명확한 명명

  • 커스텀 래퍼/함수가 고유하고 설명적인 이름을 사용하나요?
    • http.get()httpService.getWithAuth()
    • useModal()useConfirmationModal()

🔗 응집도 (Cohesion) CHECK

도메인별 구성

  • 관련된 코드가 함께 배치되어 있나요?
    • 기능별 디렉토리 구조: domains/user/, domains/product/
  • 매직넘버가 관련 로직 근처에 정의되어 있나요?

폼 응집도

  • 폼 검증 로직이 적절한 수준에서 응집되어 있나요?
    • 필드별 독립 검증 vs 폼 수준 스키마 검증
  • 관련 상태와 로직이 함께 관리되고 있나요?

⚡ 결합도 (Coupling) CHECK

상태 관리 범위

  • 상태 관리가 적절한 범위로 분리되어 있나요?
    • 전역 상태 vs 로컬 상태의 적절한 분리
  • 컴포넌트가 불필요한 상태에 의존하지 않나요?
    • useCardIdQueryParam() 같은 focused hook 사용

Props Drilling 제거

  • Props Drilling이 발생하는 경우 컴포지션 패턴을 사용했나요?
  • 중간 컴포넌트가 불필요한 props 전달을 하고 있지 않나요?

추상화 수준

  • 성급한 추상화를 피하고 적절한 중복을 허용했나요?
    • 서로 다른 유스케이스가 예상되는 경우 별도 구현 고려
  • 추상화가 과도하게 복잡하지 않나요?

📋 추가 CHECK

성능 고려사항

  • 불필요한 리렌더링을 방지했나요?
    • useCallback, useMemo 적절 사용
  • 무거운 연산을 적절히 최적화했나요?

타입 안정성

  • TypeScript 타입이 명확하고 안전하게 정의되어 있나요?
  • any 타입 사용을 최소화했나요?

테스트 가능성

  • 작성된 코드가 테스트하기 쉬운 구조인가요?
  • 순수 함수와 사이드 이펙트가 분리되어 있나요?

문서화

  • 복잡한 로직에 대한 주석이 적절히 작성되어 있나요?
  • 인터페이스나 타입에 대한 설명이 필요한 경우 추가되어 있나요?

🎨 코드 스타일 CHECK

  • 일관된 코딩 스타일을 유지하나요?
  • ESLint/Prettier 규칙을 준수하나요?
  • 불필요한 콘솔 로그나 주석이 제거되었나요?

@youngduck

Copy link
Copy Markdown
Owner Author

@gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a new blog post detailing strategies to resolve web font FOUT issues, including WOFF2 conversion, font-display settings, and font subsetting. The review feedback identifies invalid CSS syntax in code examples, suggests improving the safety of the font subsetting script by avoiding source file overwrites, and recommends consistent reporting of font size measurements throughout the article.

Comment thread _posts/React/react-3.md Outdated
Comment thread _posts/React/react-3.md
Comment on lines +143 to +159
async function run() {
const chars = "보돌코스코어드선수관리";

const boldBuffer = fs.readFileSync("public/fonts/Shilla_CultureB-Bold.woff2");
const boldSubset = await subsetFont(boldBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold.woff2", boldSubset);

const mediumBuffer = fs.readFileSync(
"public/fonts/Shilla_CultureM-Medium.woff2",
);
const mediumSubset = await subsetFont(mediumBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureM-Medium.woff2", mediumSubset);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

서브셋 생성 스크립트가 원본 폰트 파일을 직접 덮어쓰고 있습니다. 스크립트 실행 중 오류가 발생하거나 원본 파일이 다시 필요할 경우를 대비하여, 결과물을 별도의 파일명(예: ...-subset.woff2)으로 저장하는 것이 더 안전합니다.

Suggested change
async function run() {
const chars = "보돌코스코어드선수관리";
const boldBuffer = fs.readFileSync("public/fonts/Shilla_CultureB-Bold.woff2");
const boldSubset = await subsetFont(boldBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold.woff2", boldSubset);
const mediumBuffer = fs.readFileSync(
"public/fonts/Shilla_CultureM-Medium.woff2",
);
const mediumSubset = await subsetFont(mediumBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureM-Medium.woff2", mediumSubset);
}
async function run() {
const chars = "보돌코스코어드선수관리";
const boldBuffer = fs.readFileSync("public/fonts/Shilla_CultureB-Bold.woff2");
const boldSubset = await subsetFont(boldBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold-subset.woff2", boldSubset);
const mediumBuffer = fs.readFileSync(
"public/fonts/Shilla_CultureM-Medium.woff2",
);
const mediumSubset = await subsetFont(mediumBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureM-Medium-subset.woff2", mediumSubset);
}

Comment thread _posts/React/react-3.md Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a blog post detailing the optimization of web fonts to resolve FOUT issues, covering techniques like font-display: swap, WOFF2 conversion, and font subsetting. Feedback was provided regarding a syntax error in a CSS code block, a discrepancy in the character count for the subset script, inconsistent file size reporting throughout the text, and a suggestion to avoid overwriting original font files in the provided script.

Comment thread _posts/React/react-3.md Outdated

```css
@font-face {
font-family: "Shilla\*Culture";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

CSS 코드 블록 내의 font-family 명칭에 백슬래시(\)가 포함되어 있습니다. 마크다운 이스케이프 문자가 코드 블록 안에서 일반 문자로 취급된 것으로 보입니다. 실제 폰트 이름(예: "Shilla Culture")에 맞게 수정해 주세요.

Suggested change
font-family: "Shilla\*Culture";
font-family: "Shilla Culture";

Comment thread _posts/React/react-3.md

## 해결 시도 4: 서브셋 — 근본 원인인 폰트 크기줄이기

여러 방법을 시도하면서 결국 중요한 건, **폰트 파일 크기**였습니다. FOUT가 안 보이는 WantedSans는 split 서브셋으로 개당 18~45KB인 반면, 신라문화체는 한글 2,574자가 통째로 들어있어 2,405KB나 됐습니다. 실제로 프로젝트에서 사용하는 글자는 총 9글자이기때문에 나머지 글자는 다운할 필요가 없어서 서브셋처리를 통해 최적화를 해보기로 했습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

본문에는 사용 중인 글자가 '총 9글자'라고 설명되어 있으나, 아래 코드의 chars 변수에 정의된 고유 글자 수는 10개("보, 돌, 코, 스, 어, 드, 선, 수, 관, 리")입니다. 설명과 코드의 수치를 일치시키는 것이 좋습니다.

Suggested change
여러 방법을 시도하면서 결국 중요한 건, **폰트 파일 크기**였습니다. FOUT가 안 보이는 WantedSans는 split 서브셋으로 개당 18~45KB인 반면, 신라문화체는 한글 2,574자가 통째로 들어있어 2,405KB나 됐습니다. 실제로 프로젝트에서 사용하는 글자는 총 9글자이기때문에 나머지 글자는 다운할 필요가 없어서 서브셋처리를 통해 최적화를 해보기로 했습니다.
여러 방법을 시도하면서 결국 중요한 건, **폰트 파일 크기**였습니다. FOUT가 안 보이는 WantedSans는 split 서브셋으로 개당 18~45KB인 반면, 신라문화체는 한글 2,574자가 통째로 들어있어 2,405KB나 됐습니다. 실제로 프로젝트에서 사용하는 글자는 총 10글자이기때문에 나머지 글자는 다운할 필요가 없어서 서브셋처리를 통해 최적화를 해보기로 했습니다.

Comment thread _posts/React/react-3.md
const boldSubset = await subsetFont(boldBuffer, chars, {
targetFormat: "woff2",
});
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold.woff2", boldSubset);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

원본 폰트 파일을 직접 덮어쓰는 방식은 스크립트 실행 중 오류가 발생할 경우 원본 데이터가 손실될 위험이 있습니다. Shilla_CultureB-Bold.subset.woff2와 같이 별도의 파일명으로 저장하는 것을 권장합니다.

Suggested change
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold.woff2", boldSubset);
fs.writeFileSync("public/fonts/Shilla_CultureB-Bold.subset.woff2", boldSubset);

Comment thread _posts/React/react-3.md Outdated
Comment on lines +167 to +178
| Bold | 2,404 KB | 11.6 KB | 99.5% |
| Medium | 2,317 KB | 10.2 KB | 99.5% |

**2,405 kB → 약 11KB.** WantedSans의 split 파일(18~45KB)보다도 작아졌습니다.
(WantedSans는 단순히 파일이 작은 것이 아니라, unicode-range 기반으로 폰트가 여러 개의 subset 파일로 분리되어 있음)
실제로 빠른 4G 네트워크 기준으로 비교해보니 차이가 확연했습니다.

![서브셋 전후 네트워크 비교](/assets/blog/posts/react/react-3/subset-before-after.png)

| | 파일 크기 | 로딩 시간 (빠른 4G) |
| --------- | --------- | ------------------- |
| 서브셋 전 | 2,406 KB | 7.51초 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

문서 내에서 WOFF2 파일의 용량이 2,404 KB, 2,405 kB, 2,406 KB 등으로 일관되지 않게 기재되어 있습니다. 독자에게 정확한 정보를 전달하기 위해 하나의 수치로 통일해 주세요.

@youngduck
youngduck merged commit a755d94 into main May 7, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant