프론트엔드 개발 가이드은(는) 무엇을 하나요?
개요
이 가이드는 React, Vue, Angular, Svelte를 사용한 현대적인 프론트엔드 개발을 다루며, 컴포넌트 아키텍처, 성능 최적화, 접근성 및 테스트를 포함합니다. 웹 애플리케이션 구축, 컴포넌트 라이브러리 개발 또는 프론트엔드 성능 최적화 시 사용하세요.
프레임워크 및 레이아웃 결정 규칙
- 프레임워크를 선택할 때는 팀의 전문성과 프로젝트 제약 조건에 맞추십시오. 기본적으로 광범위한 생태계가 필요한 경우 React, 기존 페이지에 점진적으로 기능을 추가하는 경우 Vue, 번들 크기가 중요한 앱의 경우 Svelte, 의견이 강력하고 DI와 라우팅이 내장된 완전한 프레임워크가 필요한 경우 Angular를 선택하십시오.
- 디자인을 구현할 때는 2차원 페이지 레이아웃에는 CSS Grid를 사용하고, 1차원 컴포넌트 정렬에는 Flexbox를 사용하십시오. 레이아웃 목적으로 절대 위치 지정(absolute positioning)을 사용하지 마십시오. 반응형 리플로우를 깨뜨리기 때문입니다.
- 컴포넌트 라이브러리를 구축할 때는 각 컴포넌트를 TypeScript props 인터페이스, Storybook 스토리, 단위 테스트와 함께 명명된 내보내기(named export)로 노출하십시오. 세 가지 모두 없는 컴포넌트는 병합되지 않습니다.
- 백엔드 API와 통합할 때는 fetch 또는 axios(인터셉터 포함)를 사용하는 단일
api.ts모듈과 같은 타입이 지정된 API 클라이언트 계층에 fetch 로직을 중앙화하여 인증 헤더, 오류 변환 및 재시도가 한 곳에서 처리되도록 하십시오.
성능 결정 규칙
- 페이지의 LCP(Largest Contentful Paint)가 Lighthouse CI에서 2.5초를 초과하면 차단 버그로 처리하십시오. Chrome DevTools Performance 탭으로 프로파일링하고 병합 전에 가장 큰 병목 현상을 수정하십시오.
- 애니메이션을 추가할 때는
width,height,top,left대신 CSStransform및opacity속성(컴포지터 전용)을 사용하여 지터를 유발하는 레이아웃 재계산을 방지하십시오. - 앱에 오프라인 지원이 필요한 경우 정적 자산에는 캐시 우선(cache-first) 전략을, API 요청에는 네트워크 우선(network-first) 전략을 사용하는 서비스 워커를 등록하고, 오프라인일 때 캐시된 응답으로 폴백하십시오.
- 초기 JS 번들이 예산(예: 200KB gzipped)을 초과하는 경우
React.lazy()또는 동적import()를 사용하여 경로 기반 코드 분할을 추가하고, 중요하지 않은 스크립트는 지연 로드하십시오. - 구형 브라우저를 지원해야 하는 경우 browserslist 구성을 정의하고 빌드 도구(Vite, Webpack)가 자동 폴리필을 적용하도록 두십시오. browserslist 커버리지가 충분하지 않은 경우에만 수동으로 폴리필 또는 기능 검사를 추가하지 마십시오.
- 이미지를 추가할 때는 WebP/AVIF 소스와 명시적인
width/height속성이 있는<picture>를 사용하여 레이아웃 이동을 방지하십시오. 화면 아래 이미지에는loading="lazy"를 추가하십시오. - 초기 페이지 로드에 필요하지 않은 경로는
React.lazy()(또는 프레임워크에 해당하는 것)와<Suspense>폴백으로 감싸서 메인 번들에서 해당 경로의 코드를 제외하십시오. - 정적 자산을 제공할 때는 내용 해시된 파일 이름에는
Cache-Control: public, max-age=31536000, immutable을 설정하고,index.html에는no-cache를 설정하도록 CDN 또는 서버를 구성하십시오.
접근성 결정 규칙
- 대화형 컴포넌트를 구축할 때는 ARIA 속성, 키보드 핸들러(적절하게
Enter,Space,Escape)를 추가하고 태스크 완료를 표시하기 전에 axe-core로 테스트하십시오. - 양식을 구축할 때는
htmlFor/id를 통해 각<input>을<label>과 연결하고,aria-describedby로 연결된 표시 오류 메시지를 제공하며, 양식이 키보드 전용 탐색으로 완전히 작동 가능한지 확인하십시오. - 색상을 사용하여 의미를 전달할 때(예: 오류 상태, 상태 배지)는 항상 보조 표시기(아이콘, 텍스트, 패턴)를 포함하여 색맹 사용자가 상태를 구별할 수 있도록 하십시오.
- 모달 또는 드롭다운을 추가할 때는 요소가 열려 있는 동안 포커스를 요소 내부에 가두고 닫을 때 트리거 요소로 포커스를 되돌리십시오. 마우스 없이 전체 흐름을 탭하여 테스트하십시오.
- 풀 리퀘스트가 새로운 대화형 컴포넌트를 추가하는 경우, PR에는 병합되기 전에 WCAG 2.1 AA 위반이 0개임을 주장하는 axe-core 통합 테스트가 포함되어야 합니다.
CSS 디버깅 결정 규칙
레이아웃이 깨지면 증상별로 진단하십시오.
- Flex 자식이 컨테이너를 넘침: Flex 자식에
min-width: 0(행) 또는min-height: 0(열)을 추가하십시오. Flex 항목은 기본적으로min-width: auto이므로 콘텐츠 크기 아래로 축소되지 않습니다. - Grid 항목이 열 너비를 무시함:
1fr대신minmax(0, 1fr)을 사용하십시오. 일반1fr은minmax(auto, 1fr)을 의미하므로 콘텐츠가 열을 의도보다 넓게 밀어낼 수 있습니다. position: sticky가 작동하지 않음: 모든 상위 요소에서overflow: hidden,overflow: auto또는overflow: scroll을 확인하십시오. 이 중 하나라도 있으면 sticky 요소를 포함하는 새로운 스크롤 컨텍스트가 생성됩니다. 또한 요소에top/bottom값이 설정되어 있는지 확인하십시오.margin: auto로 중앙 정렬된 요소가 중앙에 오지 않음: 요소에 명시적인width(블록)가 있거나 부모에display: flex(플렉스 자식)가 있는지 확인하십시오.margin: auto는 전체 너비 블록 요소에서는 아무 효과가 없습니다.z-index가 작동하지 않음: 요소는 쌓임 컨텍스트를 생성하기 위해position: relative/absolute/fixed/sticky가 필요합니다. 또한 상위 요소가(transform,opacity < 1,filter또는will-change를 통해) z-index 범위를 제한하는 쌓임 컨텍스트를 생성하는지 확인하십시오.- 인라인 블록/인라인 요소 사이의 간격: HTML의 공백이 간격을 만듭니다. 해결 방법: 부모에
display: flex를 사용하거나(권장), 부모에font-size: 0을 설정하고 자식에서 재설정하십시오. - 인접 요소 사이의 1px 간격: 서브픽셀 렌더링 때문입니다. 해결 방법: 디버깅을 위해
border대신outline을 사용하거나, 부모에background를 설정하여 간격을 가리십시오. - 줄임표로 텍스트 자르기가 작동하지 않음:
overflow: hidden,white-space: nowrap,text-overflow: ellipsis세 가지가 모두 필요합니다. 여러 줄 자르기의 경우-webkit-line-clamp와display: -webkit-box및-webkit-box-orient: vertical을 사용하십시오. - 컨테이너 쿼리가 적용되지 않음: 상위 요소에
container-type: inline-size(또는size)가 있는지 확인하십시오. 요소 자체는 자체 컨테이너가 될 수 없습니다. 쿼리는 상위 요소를 참조해야 합니다.
빌드 도구 결정 규칙
- Vite(기본값): 모든 새 프로젝트에 사용하십시오. 네이티브 ESM을 통한 즉각적인 개발 서버, 빠른 HMR, Rollup 기반 프로덕션 빌드를 제공합니다. 프로젝트가 Vite에서 실패하는 CommonJS 전용 종속성을 사용하는 경우 해당 패키지에
optimizeDeps.include를 추가하십시오. Webpack으로 전환하지 마십시오. - Webpack: 이미 사용 중인 기존 프로젝트 또는 특정 Webpack 전용 플러그인에 Vite 대체재가 없는 경우에만 사용하십시오(드묾). 기회가 되면 Vite로 마이그레이션하십시오(마이그레이션 결정 규칙 참조).
- Turbopack: Next.js 14+ 내부에서만 사용하십시오(
next dev --turbo). 아직 독립형으로 사용할 수 없습니다. Next.js 프로젝트를 위해 Webpack보다 더 빠른 개발 빌드를 제공합니다. 빌드가 이미 빠른 경우(<5초) 전환하지 마십시오. - esbuild: 라이브러리 번들링, 스크립트 또는 CLI 도구에 사용하십시오. 전체 애플리케이션에는 사용하지 마십시오. HTML 진입점 처리 또는 경로별 코드 분할 기능이 없습니다. npm 패키지를 게시하려면
tsup(esbuild 래퍼)을 사용하십시오. - SWC vs Babel: SWC를 사용하십시오(Vite 플러그인 또는 Next.js 기본값을 통해). SWC는 변환 속도가 20-70배 빠릅니다. 프로젝트가 SWC에 해당하는 것이 없는 Babel 전용 플러그인에 의존하는 경우에만 Babel을 사용하십시오(점점 드물어짐).
- 모노레포 도구: 패키지 5개 미만 = npm/pnpm 워크스페이스, 추가 도구 불필요. 5-20개 패키지 = 작업 캐싱을 위한 Turborepo. 20개 이상의 패키지 또는 폴리글랏 = Nx. 단일 패키지 프로젝트에 모노레포 도구를 추가하지 마십시오.
SSR 및 서버 컴포넌트 결정 규칙
SSR vs CSR vs SSG 사용 시기
- SSG(Static Site Generation): 1시간에 한 번 미만으로 변경되는 콘텐츠(마케팅 페이지, 문서, 블로그 게시물)에 사용하십시오. 배포 시 빌드합니다. 가장 빠른 TTFB를 제공합니다.
- SSR(Server-Side Rendering): 개인화된 콘텐츠(대시보드, 사용자 프로필), 동적 데이터가 포함된 SEO 중요 페이지 또는 오래된 데이터를 허용할 수 없는 페이지에 사용하십시오. 모든 요청에 서버 지연 시간이 추가되므로
Cache-Control또는 CDN으로 적극적으로 캐싱하십시오. - CSR(Client-Side Rendering): SEO가 중요하지 않고 상호작용성이 우선인 인증된 사용자 전용 페이지(관리자 패널, 내부 도구)에 사용하십시오. 구축 및 배포가 가장 간단합니다(정적 호스팅).
- ISR(Incremental Static Regeneration): 대부분 정적이지만 주기적인 업데이트가 필요한 페이지(제품 목록, 가격 페이지)에 사용하십시오. 데이터가 얼마나 오래되어도 괜찮은지에 따라
revalidate간격을 설정하십시오: 가격의 경우 60초, 블로그 게시물의 경우 3600초.
React 서버 컴포넌트(RSC) 결정 규칙
- 기본적으로 서버 컴포넌트 사용: 상호작용성이 필요하지 않은 한 모든 컴포넌트는 서버 컴포넌트입니다. 컴포넌트에
onClick,useState,useEffect또는 브라우저 API가 있는 경우 클라이언트 컴포넌트('use client')여야 합니다. - 데이터 가져오기: 서버 컴포넌트에서
async/await를 직접 사용하여 데이터를 가져오십시오.useEffect, TanStack Query 또는 로딩 상태가 필요하지 않습니다. 데이터는 HTML이 전송되기 전에 확인됩니다. - 서버 컴포넌트의 인증: 서버 컴포넌트에서 세션/쿠키를 읽고 사용자 객체를 prop으로 클라이언트 컴포넌트에 전달하십시오. 클라이언트 컴포넌트에서 쿠키나 세션을 읽지 마십시오. 클라이언트에서 실행되기 때문입니다.
- 서버 액션 vs API 라우트 사용 시기: UI에서 호출되는 양식 제출 및 변경에는 서버 액션(
'use server')을 사용하십시오. 웹훅, 타사 통합 또는 외부 서비스에서 호출하는 엔드포인트에는 API 라우트(/api/*)를 사용하십시오. - 서버 및 클라이언트 컴포넌트 간 상태 공유: 직렬화 가능한 데이터를 서버에서 클라이언트로 prop으로 전달하십시오. 서버 데이터에서 파생된 클라이언트 측 상태가 필요한 경우 서버 prop으로
useState를 초기화하고 클라이언트에서 업데이트를 관리하십시오. - 대규모 데이터 세트:
<Suspense>경계를 사용하여 스트리밍하십시오. 느린 서버 컴포넌트를<Suspense fallback={<Skeleton />}>로 감싸십시오. 셸은 즉시 렌더링되고, 느린 컴포넌트는 준비될 때 스트리밍됩니다. Suspense 경계는 의미 있는 UI 섹션(사이드바, 메인 콘텐츠, 댓글)에 배치하고 모든 컴포넌트 주위에 배치하지 마십시오.
하이드레이션 오류 결정 트리
- 오류: "Text content does not match": 서버와 클라이언트 렌더링 간에 값이 다릅니다. 일반적인 원인:
- 렌더링에서
Date.now(),Math.random()또는new Date().toLocaleString()사용 →useEffect+useState로 이동하십시오. typeof window !== 'undefined'조건부 렌더링 →useEffect를 사용하여hasMounted플래그를 설정하십시오.- 브라우저 확장 프로그램이 DOM 노드를 주입함 → 사용자의 버그는 아니지만, 시크릿 모드에서 테스트하여 확인하십시오.
- 렌더링에서
- 오류: "Hydration failed because the server rendered HTML didn't match": 구조적 차이(다른 요소)입니다. 일반적인 원인:
<p>안에<p>중첩 또는<p>안에<div>→ 중첩을 수정하십시오. HTML 사양에서 허용되지 않습니다.- 타사 컴포넌트가 서버와 클라이언트에서 다르게 렌더링됨 →
dynamic(() => import(...), { ssr: false })로 감싸십시오. - 닫는 태그가 없거나 HTML에서 명시적인 닫기가 필요한 자체 닫기 태그.
성능 디버깅 워크플로
페이지가 느릴 때는 다음 순서를 따르십시오. 단계를 건너뛰지 마십시오.
1단계: 병목 유형 식별
- Lighthouse를 실행하십시오. 실패하는 지표를 확인하십시오.
- LCP >2.5초: 가장 큰 표시 요소가 너무 느리게 로드됩니다. 2단계로 이동하십시오.
- FID/INP >200ms: 사용자 상호작용이 JavaScript에 의해 차단됩니다. 3단계로 이동하십시오.
- CLS >0.1: 초기 페인트 후 레이아웃 이동이 발생합니다. 4단계로 이동하십시오.
2단계: LCP 수정
- LCP 요소가 무엇인지 확인하십시오(Lighthouse에 표시됨). 일반적으로: 히어로 이미지, 제목 텍스트 또는 비디오.
- 이미지인 경우:
priority(Next.js) 또는fetchpriority="high"를 추가하십시오. 명시적인width/height를 추가하십시오. WebP/AVIF로 제공하십시오. CDN에서 제공되는 경우 캐시 헤더를 확인하십시오. - 텍스트인 경우: 렌더링 차단 글꼴이 있는지 확인하십시오.
font-display: swap또는font-display: optional을 사용하십시오.<link rel="preload" as="font" crossorigin>으로 중요한 글꼴 파일을 미리 로드하십시오. - JS에 의해 차단된 경우: 번들이 너무 큽니다. 3단계를 확인하십시오. LCP 요소는 이를 생성하는 JS가 로드되고 실행될 때까지 렌더링할 수 없습니다.
- 서버 응답 시간(TTFB)을 확인하십시오. TTFB가 >600ms이면 병목 현상은 백엔드 또는 CDN입니다. 프론트엔드가 아닙니다.
3단계: INP(Interaction to Next Paint) 수정
- Chrome DevTools → Performance → 클릭/입력 상호작용을 기록하십시오.
- 긴 작업(>50ms 노란색 막대)을 찾으십시오. 클릭하여 호출 스택을 확인하십시오.
- 긴 작업이 사용자 코드인 경우:
requestIdleCallback,scheduler.yield()(Chrome 115+), 또는setTimeout(fn, 0)을 사용하여 작업을 더 작은 청크로 나누어 브라우저가 프레임 사이에 양보하도록 하십시오. - 긴 작업이 React 리렌더링인 경우: React DevTools Profiler를 여십시오. 리렌더링되는 컴포넌트를 찾으십시오. 일반적인 수정:
React.memo(비용이 많이 드는 자식),useMemo(파생된 값), 또는 상태를 사용되는 위치에 더 가깝게 이동하십시오. 단일 상태 변경으로 500개 이상의 컴포넌트가 리렌더링되면 상태가 트리에서 너무 높은 위치에 있는 것입니다. - 긴 작업이 타사 스크립트(분석, 광고)인 경우:
async또는defer속성으로 지연시키거나requestIdleCallback후에 로드하십시오.
4단계: CLS 수정
- 어떤 요소가 이동하는지 확인하십시오(Lighthouse에 표시되거나 DevTools → "Layout Shift Regions" 사용).
- 크기가 없는 이미지/비디오:
width및height속성(또는aspect-ratioCSS)을 추가하십시오. 브라우저는 자산이 로드되기 전에 공간을 예약합니다. - 글꼴 스왑: 폴백 글꼴의 메트릭이 웹 글꼴과 다릅니다.
@font-face에서size-adjust를 사용하여 메트릭을 일치시키거나,font-display: optional을 사용하여 스왑을 완전히 제거하십시오. - 접힌 부분 위에 주입된 동적 콘텐츠: 광고, 배너, 쿠키 알림. 컨테이너에
min-height로 공간을 예약하십시오. 콘텐츠 높이가 다양한 경우contain: layout을 사용하여 이동이 전파되지 않도록 하십시오. - 늦게 로드되는 CSS: 스타일시트가 첫 페인트 후에 로드되어 표시되는 레이아웃을 변경하는 경우 중요한 CSS를 인라인하거나 스타일시트를 미리 로드하십시오.
코드 품질 결정 규칙
- 테스트를 작성할 때 모든 컴포넌트에 기본 렌더링 경로를 다루는 하나 이상의 단위 테스트와 하나의 상호작용 테스트(클릭, 키보드)가 필요하도록 요구하고, 최소 80% 라인 커버리지의 CI 커버리지 게이트를 통해 시행하십시오.
- 프로젝트를 시작할 때 첫날에
tsconfig.json에서strict: true를 활성화하십시오. 나중에 strict 모드를 추가하는 것은 코드베이스가 성장함에 따라 기하급수적으로 어려워집니다. - API 호출 또는 비동기 작업이 실패하면 재시도 작업이 포함된 사용자 대상 오류 메시지를 표시하십시오. 오류를 조용히 삼키거나 원시 예외 텍스트를 표시하지 마십시오.