본문 바로가기
C.W.K.
Stream
Lesson 06 of 07 · published

Lighthouse + 접근성 바닥

~12 min · lighthouse, accessibility, a11y, performance

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
Lighthouse는 성능, 접근성, 모범 사례, SEO를 빠르게 점검해. 점수보다 실제 사용자를 막는 문제를 먼저 고치는 법을 익혀 보자.

Lighthouse 돌리기

Chrome DevTools → Lighthouse 탭 → 카테고리 (performance, accessibility, 모범 사례, SEO) 선택 → 프로덕션 모드 (dev server 아닌 npm run preview로 시작)에 돌림. 카테고리별 점수와 관련 문서 링크가 붙은 문제 목록을 받을 수 있어.

성능 점수

메트릭 구동: LCP (largest contentful paint), TBT (total blocking time), CLS (cumulative layout shift). SPA 엔 보통 LCP와 TBT가 dominant. 승리: 코드 분할 (Track 8 lesson 2), below-the-fold 이미지 lazy-load, font-display: swap, <head>. 의 렌더링-blocking JS 제거해.

접근성 바닥 (모든 SPA가 필요한 것)

  1. Semantic HTML: <div onClick> 아닌 <button>, 구조에 <nav> / <main> / <article>, 모든 input에 <label>.
  2. 키보드 네비게이션: 모든 interactive element가 Tab으로 도달 가능, 보이는 focus 링 (대체 없이 outline: none 마).
  3. 색 대비: 텍스트가 WCAG AA 통과 (보통 텍스트 4.5:1, 큰 텍스트 3:1). Lighthouse가 특정 element와 함께 실패 플래그.
  4. HTML 부족할 때 ARIA: 아이콘 전용 버튼에 aria-label, 에러 toast에 role="alert", 스트리밍 컨텐츠에 aria-live.
  5. 페이지 타이틀 &. meta: 모든 라우트가 document.title 설정 (또는 react-helmet-async 같은 라이브러리).

Best-practices 바닥

프로덕션에 HTTPS. Console 에러 없어. Deprecated API 없어. 에러 추적용 source map 가능해. 프로덕션에 CSP 헤더 (Content Security Policy). Lighthouse가 놓친 거 플래그.

접근성이 디폴트, feature 아니야. 처음부터 semantic HTML 작성 + 키보드 네비게이션 존중하면 시도 없이 Lighthouse의 대부분 a11y 체크 통과. '나중에 추가할 거' 로 취급하면 몇 주 동안 div를 button으로 리팩토링. 처음에 올바르게 하는 게 싸.

Code

Lighthouse가 찾는 흔한 접근성 문제 고치기·tsx
// 잘못된 예: div로 버튼을 흉내 내면 키보드와 접근성 의미가 사라져.
<div onClick={save} className="p-2 bg-brand">Save</div>

// 올바른 예: 의미가 있는 button은 키보드와 screen reader를 지원해.
<button
  onClick={save}
  className="p-2 bg-brand focus-visible:ring-2 focus-visible:ring-fg"
>
  Save
</button>

// 잘못된 예: 설명이 없는 image icon이 동작을 맡아.
<img src="/trash.svg" onClick={delete} />

// 올바른 예: 접근 가능한 이름이 있는 icon button을 사용해.
<button onClick={delete} aria-label="Delete conversation">
  <img src="/trash.svg" alt="" />
  {/* Button의 접근 가능한 이름이 의미를 전달하므로 alt는 비워. */}
</button>

// 잘못된 예: form input에 연결된 label이 없어.
<input name="email" />

// 올바른 예: label과 input을 연결해.
<label className="block">
  <span>Email</span>
  <input name="email" type="email" required />
</label>
라우트별 페이지 타이틀 설정·tsx
import { useEffect } from "react";

function useDocumentTitle(title: string) {
  useEffect(() => {
    const previous = document.title;
    document.title = title;
    return () => { document.title = previous; };
  }, [title]);
}

function SettingsPage() {
  useDocumentTitle("Settings — My App");
  return <h1>Settings</h1>;
}

External links

Exercise

프로젝트 빌드 (npm run build && npm run preview). Chrome DevTools → Lighthouse → 프리뷰 URL에 돌림. 상위 접근성 이슈 셋 고침. 다시 돌림. 90+ 칠 때까지 반복. 참고: 100 필수 아니야. 90+ 가 직접 기본 처리했다 보여줌.
Hint
흔한 첫 승리: 모든 input에 <label> 추가, 클릭 가능한 div를 button으로 교체, 아이콘 전용 버튼에 aria-label 추가, 본문 텍스트가 contrast 통과 보장 (Tailwind의 text-fg on bg-bg 통과되게 튠).

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.