From aea75b92aee1d1fece3ef8bbca55a3f41b8e970f Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Sat, 13 Jun 2026 02:44:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?docs(7=EC=A3=BC=EC=B0=A8):=20=EC=A2=8B?= =?UTF-8?q?=EC=9D=80=20=EB=A6=AC=EC=95=A1=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EA=B5=AC=EC=B6=95=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 "week-07/\352\271\200\353\257\274\355\230\201.md" diff --git "a/week-07/\352\271\200\353\257\274\355\230\201.md" "b/week-07/\352\271\200\353\257\274\355\230\201.md" new file mode 100644 index 0000000..d6706da --- /dev/null +++ "b/week-07/\352\271\200\353\257\274\355\230\201.md" @@ -0,0 +1,206 @@ +# ESLint를 활용한 정적 코드 분석 + +## 정적 코드 분석이란? + +정적 코드 분석은 코드를 실제로 실행하지 않고, 문제의 소지가 있는 코드를 사전에 수정하는 것을 의미한다. + +예를 들어 다음과 같은 코드가 있다고 해보자. + +```ts +function getUserName(user) { + return user.name; +} +``` + +이 코드는 `user`가 정상적으로 들어온다면 문제 없이 동작한다. 하지만 `user`가 `undefined`라면 런타임에서 에러가 발생한다. + +```ts +// Cannot read properties of undefined +``` + +모든 문제를 정적 분석으로 잡을 수 있는 것은 아니지만, 코드 실행 전에 발견할 수 있는 문제들도 많다. + +예를 들어 다음과 같은 문제는 ESLint 같은 정적 분석 도구로 미리 확인할 수 있다. + +```txt +- 사용하지 않는 변수 +- 선언되지 않은 변수 +- 잘못된 import +- useEffect 의존성 배열 누락 +``` + +즉, ESLint는 코드가 실행되기 전에 문제 가능성이 있는 부분을 미리 알려주는 안전장치라고 볼 수 있다. + +--- + +## eslint-plugin과 eslint-config + +`eslint-plugin` 이라는 접두사로 시작하는 플러그인은 여러 규칙(rule)을 제공하는 패키지이고, + +`eslint-config`는 이러한 `eslint-plugin` 묶어둔 패키지이다. + +예를 들어 React 프로젝트에서는 `eslint-plugin-react`, `eslint-plugin-react-hooks` 등을 사용해 React와 Hooks 관련 규칙을 검사할 수 있다. + +--- + +## 나만의 ESLint 규칙 만들기 + +ESLint의 장점은 이미 만들어진 규칙만 사용하는 것이 아니라, 프로젝트에 맞는 규칙을 직접 만들 수도 있다는 점이다. + +예를 들어 React 17부터는 새로운 JSX Transform이 도입되어 JSX를 사용하기 위해 매번 `import React from 'react';` 을 선언할 필요가 없다. + +기존 코드베이스에 이 구문이 많이 남아 있다면, ESLint의 `no-restricted-imports` 규칙을 활용해 기본 import를 제한할 수 있다. + +```js +export default [ + { + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react', + importNames: ['default'], + message: + 'React 17 이후 JSX 사용을 위한 기본 React import는 필요하지 않습니다.', + }, + ], + }, + ], + }, + }, +]; +``` + +--- + +## ESLint와 Prettier의 차이 + +| 구분 | Prettier | ESLint | +| -------- | ---------------------- | ---------------------------- | +| 핵심 역할 | 코드 스타일 및 포맷 정리 | 코드 품질 및 위험 요소 점검 | +| 주요 목적 | 코드 형태의 일관성 유지 | 잠재적인 버그와 잘못된 패턴 방지 | +| 관리 대상 | 줄바꿈, 들여쓰기, 따옴표, 세미콜론 등 | 사용하지 않는 변수, 잘못된 조건문, 전역 변수 등 | +| 적용 방식 | 저장 시 또는 커밋 전 자동 포맷 적용 | 규칙 위반 시 경고 또는 에러 표시 | +| 팀 운영 관점 | 스타일 논쟁 최소화 | 팀의 코드 기준을 규칙으로 고정 | +| 대체 가능 여부 | ESLint로 대체 불가 | Prettier로 대체 불가 | + +--- + + +# React Testing Library + +React Testing Library란 리액트 공식 문서에서도 사용을 권장하는 UI 컴포넌트 테스트 라이브러리이다. + +컴포넌트의 내부 상태나 구현 방식이 아닌, **사용자가 화면에서 실제로 상호작용하는 방식**을 그대로 테스트하도록 설계된 것이 가장 큰 특징이다. + +예를 들어 다음과 같은 컴포넌트가 있다고 해보자. + +```tsx +function LoginForm() { + const [email, setEmail] = useState(''); + + return ( + <> + setEmail(e.target.value)} + /> + + + ); +} +``` + +이 컴포넌트를 테스트한다고 했을 때 이런 생각을 하기 쉽다. + +```txt +- state 값이 변경되었는가? +- setEmail 함수가 호출되었는가? +``` + +하지만 사용자는 이런 것을 전혀 알지 못한다. + +
+ +사용자는 + +'입력 창이 보인다', '이메일을 입력한다', '로그인 버튼이 활성화 된다.' 등 + +실제로 보이는 것에 의존한다. + +즉, 테스트도 **사용자가 경험하는 흐름**을 기준으로 작성해야 한다는 것이 React Testing Library의 핵심 철학이다. + +--- + +## React Testing Library 예시 + +예를 들어 로그인 폼이 있다고 해보자. + +```tsx +export function LoginForm() { + const [email, setEmail] = useState(''); + + return ( +
+ + + setEmail(e.target.value)} + /> + + +
+ ); +} +``` + +이 컴포넌트에서 사용자가 실제로 경험하는 흐름은 다음과 같다. + +```txt +1. 로그인 버튼은 처음에 비활성화 상태다. +2. 이메일을 입력한다. +3. 로그인 버튼이 활성화된다. +``` + +테스트도 그대로 작성한다. + +```tsx +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +test('이메일을 입력하면 로그인 버튼이 활성화된다.', async () => { + const user = userEvent.setup(); + + render(); + + const input = screen.getByLabelText('이메일'); + const button = screen.getByRole('button', { + name: '로그인', + }); + + expect(button).toBeDisabled(); + + await user.type(input, 'test@example.com'); + + expect(button).toBeEnabled(); +}); + +``` + +
+ +작성된 테스트 코드를 보면, 앞서 정리한 사용자의 행동 흐름이 그대로 녹아있다는 것을 알 수 있다. + +* ``: 사용자가 화면에서 '이메일'이라는 라벨을 보고 입력창을 찾는 과정을 뜻한다. +* ``: 사용자가 실제로 키보드를 두드려 이메일을 입력하는 행위를 시뮬레이션한다. +* ``: 이메일이 입력된 후, 사용자 눈에 버튼이 활성화되어 보이는지 검증한다. + +테스트 어디에서도 `email`이라는 state가 잘 바뀌었는지, `setEmail` 함수가 실행되었는지는 확인하지 않는다. + +오직 **사용자에게 무엇이 보이고, 사용자가 어떤 상호작용을 하는지**에만 집중할 뿐이다. From bcf621a3fd43a62ae67de9be5cdae95d6251a834 Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Sat, 13 Jun 2026 17:12:17 +0900 Subject: [PATCH 2/3] =?UTF-8?q?docs(7=EC=A3=BC=EC=B0=A8):=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20=EC=9B=B9=20=EA=B0=9C=EB=B0=9C=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EA=B4=80=EC=8B=AC=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=95=BC=20?= =?UTF-8?q?=ED=95=A0=20=ED=95=B5=EC=8B=AC=20=EC=A7=80=ED=91=9C=201?= =?UTF-8?q?=EC=B0=A8=20=EB=82=B4=EC=9A=A9=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 617 ++++++++++++++++++ 1 file changed, 617 insertions(+) diff --git "a/week-07/\352\271\200\353\257\274\355\230\201.md" "b/week-07/\352\271\200\353\257\274\355\230\201.md" index d6706da..040d21c 100644 --- "a/week-07/\352\271\200\353\257\274\355\230\201.md" +++ "b/week-07/\352\271\200\353\257\274\355\230\201.md" @@ -204,3 +204,620 @@ test('이메일을 입력하면 로그인 버튼이 활성화된다.', async () 테스트 어디에서도 `email`이라는 state가 잘 바뀌었는지, `setEmail` 함수가 실행되었는지는 확인하지 않는다. 오직 **사용자에게 무엇이 보이고, 사용자가 어떤 상호작용을 하는지**에만 집중할 뿐이다. + +--- + +
+ +# 핵심 웹 지표란? + +핵심 웹 지표(Core Web Vitals)는 구글에서 만든 지표로, 웹페이지의 사용자 경험을 측정하기 위해 제시한 성능 지표다. + +핵심 웹 지표로 다음 세 가지를 다룬다. + +* LCP: Largest Contentful Paint (최대 콘텐츠풀 페인트) +* FID: First Input Delay (최초 입력 지연) +* CLS: Cumulative Layout Shift (누적 레이아웃 이동) + +다만 현재 기준에서는 `FID`가 `INP`로 대체되었다. + +따라서 `FID`를 먼저 이해하되, 실제 서비스 성능을 점검할 때는 `INP`까지 함께 확인하는 것이 좋다. + +--- + +# LCP (최대 콘텐츠풀 페인트) + +## LCP란? + +`LCP(Largest Contentful Paint)`는 페이지가 처음 로드되기 시작한 시점부터, 뷰포트 안에서 가장 큰 요소가 화면에 렌더링되기까지 걸리는 시간을 의미한다. + +여기서 뷰포트는 사용자가 현재 보고 있는 화면 영역을 말한다. + +LCP의 측정 대상이 될 수 있는 요소는 다음과 같다. + +* `` +* `` 내부의 `` +* `poster` 속성을 사용하는 `` 태그는 HTML 파싱 과정에서 브라우저의 프리로드 스캐너(preload scanner)에 의해 빠르게 발견된다. + +프리로드 스캐너는 HTML 파싱을 차단하지 않고 이미지, 폰트 등 우선적으로 로딩하면 좋은 리소스를 먼저 찾아 병렬로 다운로드하는 기능이다. + +따라서 HTML 파싱이 완료되지 않았더라도 ``는 빠르게 요청될 수 있으며, `` 역시 동일한 방식으로 동작한다. + +반면 SVG 내부에 포함된 이미지 리소스는 상황이 다르다. + +```html + + + +``` + +이 경우 SVG 내부 이미지가 모두 로드되어야 최대 콘텐츠가 완성된 것으로 판단될 수 있다. 따라서 LCP 측면에서는 일반적인 `` 태그보다 불리할 수 있다. + +결론적으로 화면의 주요 이미지라면 단순한 배경 이미지나 SVG 내부 이미지보다 `` 또는 ``를 사용하는 편이 유리하다. + +--- + +### 5. LCP 리소스는 가능하면 동일 출처에서 직접 호스팅하기 + +LCP 후보가 되는 이미지나 비디오 같은 리소스는 가능하면 현재 서비스와 동일한 출처(origin)에서 직접 제공하는 것이 좋다. + +예를 들어 다음과 같은 경우를 생각해볼 수 있다. + +```html + +``` + +브라우저는 해당 리소스를 가져오기 위해 다음 과정을 추가로 수행해야 한다. + +```text +DNS 조회 +→ TCP 연결 +→ TLS 핸드셰이크 +→ 리소스 요청 +→ 응답 수신 +``` + +이미 연결이 맺어져 있는 동일 출처의 리소스보다 네트워크 비용이 더 발생할 수 있다. + +특히 LCP 이미지처럼 사용자 경험에 직접 영향을 주는 리소스라면 외부 출처에서 가져오기보다 서비스에서 직접 호스팅하거나, 최소한 사전에 연결을 준비할 수 있도록 최적화하는 것이 좋다. + +--- + +### 6. LCP 요소에 불필요한 애니메이션 적용하지 않기 + +사용자가 가장 먼저 봐야 하는 콘텐츠에 `fadeIn`, `slideIn` 같은 애니메이션을 적용하면 시각적으로는 자연스러워 보일 수 있다. + +하지만 브라우저는 애니메이션이 완료되어 실제로 콘텐츠가 표시되는 시점을 기준으로 LCP를 측정할 수 있다. + +```css +.hero { + opacity: 0; + animation: fadeIn 1s ease forwards; +} +``` + +위와 같은 경우 사용자는 콘텐츠가 존재하더라도 애니메이션이 끝날 때까지 주요 콘텐츠를 제대로 인식하지 못할 수 있다. + +따라서 LCP 후보가 되는 영역에는 다음을 고려하는 것이 좋다. + +* 불필요한 진입 애니메이션 제거 +* 애니메이션 시간을 최소화 +* 핵심 콘텐츠는 즉시 표시 + +사용자에게 가장 중요한 콘텐츠는 가능한 한 즉시 보여주는 것이 좋다. + +--- + +### 7. video 요소에는 poster를 제공하기 + +`poster`는 사용자가 비디오를 재생하거나 탐색하기 전까지 화면에 표시되는 대표 이미지다. + +```html + +``` + +`poster` 이미지는 ``와 마찬가지로 프리로드 스캐너에 의해 빠르게 발견되어 요청될 수 있다. + +반대로 `poster`가 없는 경우 브라우저는 비디오 자체를 로드한 뒤 첫 번째 프레임을 추출해 화면에 표시해야 한다. 이 과정은 이미지 하나를 불러오는 것보다 비용이 크기 때문에 LCP에 악영향을 줄 수 있다. + +따라서 비디오가 LCP 후보가 될 수 있는 영역이라면 반드시 `poster`를 제공하는 것이 좋다. + +--- + +### 8. 클라이언트에서 LCP 영역을 늦게 만들지 않기 + +다음과 같이 `useEffect` 이후 API 응답을 받아 주요 콘텐츠를 보여주는 구조라면 LCP가 늦어질 수 있다. + +```tsx +function MainContent() { + const [show, setShow] = useState(false); + + useEffect(() => { + async function load() { + const result = await fetch("/api/main"); + + if (result.ok) { + setShow(true); + } + } + + load(); + }, []); + + if (!show) { + return null; + } + + return
메인 콘텐츠
; +} +``` + +이 구조에서는 다음 과정이 모두 끝나야 주요 콘텐츠가 나타난다. + +```text +HTML 로드 +→ JavaScript 다운로드 +→ React 실행 +→ useEffect 실행 +→ API 요청 +→ 응답 수신 +→ 상태 변경 +→ 렌더링 +``` + +LCP 영역은 가능하면 서버에서 미리 렌더링되거나, 초기 HTML에 포함되는 편이 좋다. + +--- + +# FID + +## FID란? + +`FID(First Input Delay)`는 사용자가 페이지와 처음 상호작용한 시점부터, 브라우저가 해당 이벤트 처리를 시작할 수 있을 때까지 걸리는 시간을 의미한다. + +예를 들어 사용자가 버튼을 클릭했는데 브라우저의 메인 스레드가 바쁘다면, 클릭 이벤트 처리가 바로 시작되지 못한다. +이때 발생하는 지연 시간이 FID다. + +```text +사용자 클릭 +→ 브라우저가 바로 처리하지 못함 +→ 메인 스레드 작업이 끝남 +→ 이벤트 핸들러 실행 시작 +``` + +FID는 이벤트 핸들러가 실행되는 데 걸리는 전체 시간이 아니라, **이벤트 처리를 시작하기 전까지의 지연 시간**을 측정한다. + +--- + +## FID가 나빠지는 이유 + +FID가 나빠지는 가장 큰 이유는 브라우저의 메인 스레드가 바쁘기 때문이다. + +브라우저의 메인 스레드는 JavaScript 실행, 렌더링, 스타일 계산, 레이아웃 계산 등 많은 작업을 처리한다. + +특히 JavaScript는 기본적으로 싱글 스레드로 동작하기 때문에, 무거운 JavaScript 작업이 실행 중이라면 사용자의 클릭이나 입력 이벤트를 즉시 처리하지 못한다. + +예를 들어 다음과 같은 상황에서 FID가 나빠질 수 있다. + +* 초기 로딩 시 JavaScript 번들이 너무 큼 +* 사용하지 않는 코드가 많이 포함됨 +* 무거운 연산이 메인 스레드를 오래 점유함 +* 타사 스크립트가 초기 로딩 중 실행됨 +* 폴리필이나 라이브러리가 과하게 포함됨 + +--- + +## FID 기준 + +책에서 다루는 FID 기준은 다음과 같다. + +```text +100ms 이하 → 좋음 +100ms ~ 300ms → 개선 필요 +300ms 초과 → 나쁨 +``` + +사용자는 아주 짧은 지연에도 민감하게 반응한다. +따라서 클릭, 입력, 탭 같은 상호작용은 최대한 빠르게 처리될 수 있어야 한다. + +--- + +## FID 개선 방법 + +### 1. Long Task 분리하기 + +메인 스레드를 오래 점유하는 작업을 Long Task라고 한다. + +긴 작업 하나가 메인 스레드를 계속 차지하면, 그동안 사용자의 입력을 처리할 수 없다. +따라서 무거운 작업은 작은 단위로 나누거나, 초기 로딩 이후로 미루는 것이 좋다. + +--- + +### 2. JavaScript 코드 줄이기 + +초기 JavaScript 번들이 클수록 다운로드, 파싱, 실행에 시간이 오래 걸린다. + +React 애플리케이션에서는 다음과 같은 방법을 고려할 수 있다. + +* 코드 스플리팅 +* `React.lazy` +* `Suspense` +* Next.js의 `dynamic import` +* 사용하지 않는 라이브러리 제거 +* 무거운 기능은 필요한 시점에 로드 + +```tsx +const HeavyComponent = lazy(() => import("./HeavyComponent")); +``` + +초기 화면에 필요하지 않은 코드는 처음부터 불러오지 않는 것이 좋다. + +--- + +### 3. 타사 스크립트 실행 지연하기 + +Google Analytics, 광고 스크립트, 채팅 위젯, Firebase 등 타사 스크립트는 성능에 영향을 줄 수 있다. + +이런 스크립트가 초기 로딩에 반드시 필요하지 않다면 `async`, `defer`를 사용해 실행 시점을 늦추는 것이 좋다. + +```html + +``` + +초기 사용자 경험에 꼭 필요하지 않은 스크립트는 최대한 뒤로 미루는 것이 좋다. + +--- + +# INP + +## 현재는 FID보다 INP가 중요하다 + +책에서는 FID를 핵심 웹 지표로 설명하지만, 현재 Core Web Vitals에서는 FID 대신 `INP(Interaction to Next Paint)`가 사용된다. + +FID는 사용자의 첫 번째 입력만 측정한다. +하지만 실제 서비스에서는 첫 번째 클릭뿐만 아니라, 페이지를 사용하는 전체 과정에서의 반응성이 중요하다. + +INP는 사용자가 페이지에 머무는 동안 발생한 클릭, 탭, 키보드 입력 등의 상호작용을 관찰하고, 그중 느린 상호작용을 기준으로 페이지의 전반적인 반응성을 평가한다. + +즉, FID가 “첫인상”에 가까운 지표라면, INP는 “페이지를 사용하는 전체 과정에서의 반응성”에 더 가깝다. + +--- + +## INP 기준 + +현재 기준에서 INP는 다음과 같이 판단한다. + +```text +200ms 이하 → 좋음 +200ms ~ 500ms → 개선 필요 +500ms 초과 → 나쁨 +``` + +INP를 개선하려면 결국 사용자의 상호작용에 대해 브라우저가 빠르게 다음 화면을 그릴 수 있어야 한다. + +이를 위해서는 다음을 고려해야 한다. + +* 이벤트 핸들러 내부 작업 줄이기 +* 불필요한 리렌더링 줄이기 +* 무거운 계산은 메모이제이션 또는 Web Worker 고려 +* DOM 크기 줄이기 +* 복잡한 CSS 선택자나 레이아웃 계산 줄이기 +* 상호작용 직후 실행되는 동기 작업 최소화 + +--- + +# CLS + +## CLS란? + +`CLS(Cumulative Layout Shift)`는 페이지의 생명주기 동안 발생하는 예상치 못한 레이아웃 이동의 누적 점수를 의미한다. + +사용자가 버튼을 누르려고 하는 순간 갑자기 광고 배너가 나타나 버튼 위치가 밀린다면, 사용자는 원하지 않는 요소를 클릭할 수 있다. + +이런 경험은 사용자를 매우 불편하게 만든다. + +CLS는 이런 시각적 불안정성을 측정한다. + +--- + +## CLS가 발생하는 예시 + +다음과 같이 렌더링 이후 비동기 요청 결과에 따라 배너를 추가하는 경우를 생각해볼 수 있다. + +```tsx +function Banner() { + const [show, setShow] = useState(false); + + useEffect(() => { + async function fetchBanner() { + const result = await fetch("/api/banner"); + + if (result.ok) { + setShow(true); + } + } + + fetchBanner(); + }, []); + + if (!show) { + return null; + } + + return
이벤트 진행 중!
; +} +``` + +처음에는 배너가 없다가, API 응답 후 배너가 갑자기 생긴다. +이때 기존 콘텐츠가 아래로 밀리면 레이아웃 이동이 발생한다. + +사용자 입장에서는 보고 있던 콘텐츠 위치가 갑자기 바뀌기 때문에 불안정한 화면으로 느껴진다. + +--- + +## CLS 기준 + +CLS는 다음 기준으로 판단한다. + +```text +0.1 이하 → 좋음 +0.1 ~ 0.25 → 개선 필요 +0.25 초과 → 나쁨 +``` + +CLS는 낮을수록 좋다. + +--- + +## CLS 개선 방법 + +### 1. 이미지 크기 미리 지정하기 + +이미지의 `width`, `height`를 지정하지 않으면, 이미지가 로딩되기 전까지 브라우저가 해당 이미지의 공간을 정확히 알 수 없다. + +이미지가 뒤늦게 로드되면서 주변 콘텐츠가 밀리면 CLS가 발생한다. + +```html +썸네일 +``` + +반응형 이미지라면 `aspect-ratio`를 사용하는 것도 도움이 된다. + +```css +.image-box { + aspect-ratio: 4 / 3; +} +``` + +--- + +### 2. 동적 콘텐츠가 들어올 공간 미리 확보하기 + +광고, 배너, 추천 영역, 알림 영역처럼 나중에 삽입될 수 있는 요소는 미리 공간을 확보해두는 것이 좋다. + +```tsx +function BannerSkeleton() { + return
; +} +``` + +스켈레톤 UI를 사용하면 사용자는 로딩 중임을 인식할 수 있고, 브라우저는 미리 공간을 확보할 수 있다. + +--- + +### 3. 폰트 로딩 최적화하기 + +웹 폰트도 레이아웃 이동의 원인이 될 수 있다. + +폰트 로딩 과정에서 다음과 같은 현상이 발생할 수 있다. + +* FOUT: 기본 폰트가 먼저 보이다가 웹 폰트로 교체되는 현상 +* FOIT: 폰트가 로딩되기 전까지 텍스트가 보이지 않는 현상 + +폰트가 바뀌면서 글자의 크기나 줄바꿈이 달라지면 레이아웃 이동이 발생할 수 있다. + +이를 줄이기 위해 다음 방법을 사용할 수 있다. + +* 중요한 폰트는 `preload`로 먼저 로드 +* `font-display: optional` 또는 `swap` 사용 +* 기본 폰트와 웹 폰트의 크기 차이를 줄이기 + +```css +@font-face { + font-family: "MyFont"; + src: url("/fonts/my-font.woff2") format("woff2"); + font-display: optional; +} +``` + +--- + +### 4. useEffect 이후 화면 구조가 바뀌는 작업 줄이기 + +`useEffect`는 렌더링이 끝난 이후 실행된다. +따라서 `useEffect` 안에서 화면의 높이나 위치를 바꾸는 작업이 많아지면 CLS가 나빠질 수 있다. + +렌더링 이후 갑자기 요소를 추가하기보다는 다음과 같은 방식이 좋다. + +* 서버에서 미리 필요한 데이터를 내려준다. +* 스켈레톤 UI로 공간을 확보한다. +* 초기 화면 영역에는 갑작스러운 삽입을 피한다. +* 사용자 액션 이후 발생하는 변화로 만든다. + +--- + +# 핵심 웹 지표는 아니지만 함께 보면 좋은 지표 + +## TTFB + +`TTFB(Time To First Byte)`는 브라우저가 서버에 요청을 보낸 뒤, 첫 번째 바이트를 받기까지 걸리는 시간이다. + +TTFB는 서버 응답 속도와 관련이 깊다. + +특히 SSR을 사용하는 애플리케이션에서는 TTFB를 주의 깊게 봐야 한다. +서버에서 HTML을 만들기 위해 많은 작업을 수행하거나, 서버에서 호출하는 API가 느리면 TTFB가 길어질 수 있다. + +TTFB를 개선하려면 다음을 고려할 수 있다. + +* 서버 로직 최적화 +* SSR 과정에서 필요한 API 호출 최적화 +* 캐싱 활용 +* 사용자와 가까운 리전의 서버 사용 +* CDN 활용 +* 스트리밍 렌더링 고려 + +--- + +## FCP + +`FCP(First Contentful Paint)`는 페이지 로드가 시작된 시점부터 화면에 첫 번째 콘텐츠가 렌더링되기까지 걸리는 시간이다. + +여기서 콘텐츠는 텍스트, 이미지, SVG, canvas 등을 의미한다. + +FCP는 사용자가 “아무것도 없는 빈 화면”에서 벗어나는 시점을 측정한다. + +```text +빈 화면 +→ 첫 텍스트 또는 이미지 표시 +→ FCP 발생 +``` + +FCP를 개선하려면 다음을 고려할 수 있다. + +* TTFB 개선 +* 렌더링을 막는 CSS, JavaScript 최소화 +* 초기 화면에 필요한 리소스 우선 로딩 +* 불필요한 리다이렉트 제거 +* DOM 크기 줄이기 + +--- + +# 정리 + +핵심 웹 지표는 단순히 개발자가 성능을 수치로 확인하기 위한 도구가 아니다. + +사용자가 웹사이트를 이용하면서 느끼는 다음 경험을 측정하기 위한 지표다. + +```text +LCP → 주요 콘텐츠가 빠르게 보이는가? +FID / INP → 사용자의 입력에 빠르게 반응하는가? +CLS → 화면이 갑자기 흔들리지 않는가? +``` + +좋은 웹사이트는 기능이 많은 웹사이트가 아니라, 사용자가 목적을 빠르고 안정적으로 달성할 수 있는 웹사이트다. + +따라서 성능 최적화는 배포 직전에 한 번 확인하는 작업이 아니라, 개발 과정 전반에서 계속 의식해야 하는 품질 기준이라고 볼 수 있다. + From 5b7a2d50ba4a2402f72975902a1e88fa02816399 Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Sun, 14 Jun 2026 00:55:20 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs(7=EC=A3=BC=EC=B0=A8):=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20=EC=9B=B9=20=EA=B0=9C=EB=B0=9C=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EA=B4=80=EC=8B=AC=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=95=BC=20?= =?UTF-8?q?=ED=95=A0=20=ED=95=B5=EC=8B=AC=20=EC=A7=80=ED=91=9C=20-=20?= =?UTF-8?q?=EB=A7=88=EB=AC=B4=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 85 ++----------------- 1 file changed, 5 insertions(+), 80 deletions(-) diff --git "a/week-07/\352\271\200\353\257\274\355\230\201.md" "b/week-07/\352\271\200\353\257\274\355\230\201.md" index 040d21c..9872801 100644 --- "a/week-07/\352\271\200\353\257\274\355\230\201.md" +++ "b/week-07/\352\271\200\353\257\274\355\230\201.md" @@ -477,7 +477,7 @@ LCP 영역은 가능하면 서버에서 미리 렌더링되거나, 초기 HTML --- -# FID +# FID (최초 입력 지연) ## FID란? @@ -486,15 +486,10 @@ LCP 영역은 가능하면 서버에서 미리 렌더링되거나, 초기 HTML 예를 들어 사용자가 버튼을 클릭했는데 브라우저의 메인 스레드가 바쁘다면, 클릭 이벤트 처리가 바로 시작되지 못한다. 이때 발생하는 지연 시간이 FID다. -```text -사용자 클릭 -→ 브라우저가 바로 처리하지 못함 -→ 메인 스레드 작업이 끝남 -→ 이벤트 핸들러 실행 시작 -``` - FID는 이벤트 핸들러가 실행되는 데 걸리는 전체 시간이 아니라, **이벤트 처리를 시작하기 전까지의 지연 시간**을 측정한다. +만약 이벤트 핸들러의 실행 시간을 측정하고 싶다면 Event Timing API를 사용하는 것이 좋다. + --- ## FID가 나빠지는 이유 @@ -554,10 +549,6 @@ React 애플리케이션에서는 다음과 같은 방법을 고려할 수 있 * 사용하지 않는 라이브러리 제거 * 무거운 기능은 필요한 시점에 로드 -```tsx -const HeavyComponent = lazy(() => import("./HeavyComponent")); -``` - 초기 화면에 필요하지 않은 코드는 처음부터 불러오지 않는 것이 좋다. --- @@ -572,8 +563,6 @@ Google Analytics, 광고 스크립트, 채팅 위젯, Firebase 등 타사 스크 ``` -초기 사용자 경험에 꼭 필요하지 않은 스크립트는 최대한 뒤로 미루는 것이 좋다. - --- # INP @@ -587,7 +576,7 @@ FID는 사용자의 첫 번째 입력만 측정한다. INP는 사용자가 페이지에 머무는 동안 발생한 클릭, 탭, 키보드 입력 등의 상호작용을 관찰하고, 그중 느린 상호작용을 기준으로 페이지의 전반적인 반응성을 평가한다. -즉, FID가 “첫인상”에 가까운 지표라면, INP는 “페이지를 사용하는 전체 과정에서의 반응성”에 더 가깝다. +즉, FID가 '첫인상'에 가까운 지표라면, INP는 '페이지를 사용하는 전체 과정에서의 반응성'에 더 가깝다. --- @@ -614,7 +603,7 @@ INP를 개선하려면 결국 사용자의 상호작용에 대해 브라우저 --- -# CLS +# CLS (누적 레이아웃 이동) ## CLS란? @@ -757,67 +746,3 @@ function BannerSkeleton() { * 초기 화면 영역에는 갑작스러운 삽입을 피한다. * 사용자 액션 이후 발생하는 변화로 만든다. ---- - -# 핵심 웹 지표는 아니지만 함께 보면 좋은 지표 - -## TTFB - -`TTFB(Time To First Byte)`는 브라우저가 서버에 요청을 보낸 뒤, 첫 번째 바이트를 받기까지 걸리는 시간이다. - -TTFB는 서버 응답 속도와 관련이 깊다. - -특히 SSR을 사용하는 애플리케이션에서는 TTFB를 주의 깊게 봐야 한다. -서버에서 HTML을 만들기 위해 많은 작업을 수행하거나, 서버에서 호출하는 API가 느리면 TTFB가 길어질 수 있다. - -TTFB를 개선하려면 다음을 고려할 수 있다. - -* 서버 로직 최적화 -* SSR 과정에서 필요한 API 호출 최적화 -* 캐싱 활용 -* 사용자와 가까운 리전의 서버 사용 -* CDN 활용 -* 스트리밍 렌더링 고려 - ---- - -## FCP - -`FCP(First Contentful Paint)`는 페이지 로드가 시작된 시점부터 화면에 첫 번째 콘텐츠가 렌더링되기까지 걸리는 시간이다. - -여기서 콘텐츠는 텍스트, 이미지, SVG, canvas 등을 의미한다. - -FCP는 사용자가 “아무것도 없는 빈 화면”에서 벗어나는 시점을 측정한다. - -```text -빈 화면 -→ 첫 텍스트 또는 이미지 표시 -→ FCP 발생 -``` - -FCP를 개선하려면 다음을 고려할 수 있다. - -* TTFB 개선 -* 렌더링을 막는 CSS, JavaScript 최소화 -* 초기 화면에 필요한 리소스 우선 로딩 -* 불필요한 리다이렉트 제거 -* DOM 크기 줄이기 - ---- - -# 정리 - -핵심 웹 지표는 단순히 개발자가 성능을 수치로 확인하기 위한 도구가 아니다. - -사용자가 웹사이트를 이용하면서 느끼는 다음 경험을 측정하기 위한 지표다. - -```text -LCP → 주요 콘텐츠가 빠르게 보이는가? -FID / INP → 사용자의 입력에 빠르게 반응하는가? -CLS → 화면이 갑자기 흔들리지 않는가? -``` - -좋은 웹사이트는 기능이 많은 웹사이트가 아니라, 사용자가 목적을 빠르고 안정적으로 달성할 수 있는 웹사이트다. - -따라서 성능 최적화는 배포 직전에 한 번 확인하는 작업이 아니라, 개발 과정 전반에서 계속 의식해야 하는 품질 기준이라고 볼 수 있다. -