웹 성능 최적화 - 불필요한 css 파일 줄이기

최정은·2026년 8월 20일

pin-plate

목록 보기
6/7
post-thumbnail

너무 해보고 싶었던 성능 최적화…!!!!!!
몇년전부터 성능 최적화를 해보고 싶었지만, 기회가 오지 않아(핑계일 수도 있다) 하지 못했었다.
개인 프로젝트를 진행하면서 이번 기회에 한 번 해봐야겠다고 실천했다.

성능 최적화 방법

Chrome > light house에 들어가서 측정하면 된다.

Page speed

https://pagespeed.web.dev/ 사이트에 들어가서 측정을 원하는 URL 주소를 입력해도 된다.


성능, 접근성, 권장 사항, 검색엔진 최적화 심지어 에이전트형 브라우징인지 알려준다.

하지만 로그인해야 접속할 수 있는 페이지는 측정할 수 없는 단점이 존재한다.

Light house

내가 만든 프로젝트는 로그인해야 주요 화면을 볼 수 있어서 크롬 브라우저에서 지원하는 light house를 이용하여 성능 측정을 해보았다.

예상대로 성능이 그렇게 좋지 않았다.

어떤 부분에서 성능이 안 좋은지 자세히 알아보자.

FCP, LCP의 점수가 낮은 것을 확인할 수 있다.

FCP, LCP란?

먼저 LCP에 대해서 알아보자.

구글에서는 Web vitals라는 웹에서 우수한 사용자 경험을 제공하는 데 필수적인 통합된 안내를 제공한다.

그중에서 Core Web Vitals는 Web Vitals의 하위 집합인데, 모든 사이트 소유자가 측정하고 모든 구글 도구에 표시된다고 한다.

현재는 사용자 경험의 세 가지 측면인 로드, 상호작용성, 시각적 안정성에 중점을 두고 있다고 한다.

  • LCP : 화면에서 보이는 주요 콘텐츠가 렌더링 된 시간
  • INP : 어떤 상호작용을 한 뒤 화면이 그려질 때까지의 시간
  • CLS : 예기치 않은 레이아웃 변경에 대한 점수 중 가장 큰 점수를 측정한 것. 보통 Layout shift가 일어날 경우를 측정한다.

LCP

LCP 같은 경우엔 2.5초 이하일 경우가 좋다고 한다. 그럼 어떤 걸 보고 LCP를 측정하는 걸까?

  • <img> 요소 (첫 번째 프레임 표시 시간은 GIF 또는 애니메이션 PNG와 같은 애니메이션 콘텐츠에 사용됨)
  • <svg> 요소 내의 <image> 요소
  • <video> 요소 (포스터 이미지 로드 시간 또는 동영상의 첫 번째 프레임 표시 시간 중 더 빠른 값 사용)
  • CSS 그라데이션이 아닌 url() 함수를 사용하여 로드된 배경 이미지가 있는 요소
  • 텍스트 노드 또는 다른 인라인 수준 텍스트 요소 하위 요소가 포함된 블록 수준 요소입니다.

나의 사이트를 측정했을 땐 LCP가 2.5초 이상 넘어갔다. 그러면 어떻게 개선해야 할까?

두 가지를 살펴보도록 했다.

  • 초기 HTML 문서
  • LCP 리소스

LCP 리소스를 살피려면 개발자 도구를 사용하여 확인하면 된다고 한다. 네트워크 탭으로 가서 LCP 리소스(image, video 등)가 얼마나 로드가 걸렸는지 확인하면 된다고 한다.

최상의 LCP 시간을 달성하려면 모든 하위 요소가 최적화가 되어야 한다.

예를 들어서 이미지를 WebP나 AVIF로 바꾸면 파일 크기가 줄어 다운로드는 빨라질 수 있다. 하지만 이미지를 다 받은 뒤 브라우저가 화면에 그리는 데 시간이 오래 걸린다면, 최종 LCP는 크게 줄지 않을 수 있다.

하위 요소는 아래와 같다.

구글에서 추천하는 LCP를 개선하는 방향성은 아래와 같다.

  • LCP 시간의 대부분은 HTML 문서와 LCP 소스를 로드하는 데 사용되어야 한다.
  • 이러한 두 리소스 중 하나가 LCP 전에 로드되지 않는다면 개선이 필요하다.

각 부분 최적화 방법

  1. 리소스 로드 지연 제거
  2. 요소 렌더링 지연 제거
  3. 리소스 로드 시간 줄이기
  4. 첫 바이트까지의 시간 단축 (TTFB)

먼저 리소스 로드 지연 제거에 대해서 살펴보겠다.

일반적으로 LCP 리소스는 페이지에서 로드된 첫 번째 리소스와 동시에 로드되기 시작해야한다.

LCP 리소스가 최대한 빠르게 로드되도록 하려면 브라우저의 preload scanner가 초기 HTML 문서 응답에서 리소스를 검색할 수 있어야 한다.

💡 Browser preload scanner란?
원시 마크업에서 LCP 리소스가 어디 있는지 미리 파악하고 로드를 시작하도록 하는 보조 HTML 파서라고 한다.

마크업을 토큰화하고 DOM으로 만드는 기본 HTML 파서가 존재한다. 이 파서는 link나 script를 만나기 전까지 파싱을 하다가 만나면 중단하고 리소스를 로드하기 시작한다.

이렇게 파싱과 렌더링을 모두 차단하게 되면 다른 중요한 리소스의 검색이 지연되어서 프로그램이 중단된다고 한다.

그래서 화면에 바로 보이는 리소스에게 우선순위를 높게 줘서 빠르게 로드를 하도록 한다.

두번째는 요소 렌더링 지연을 제거하는 것이다.

리소스 로드가 언제 완료되든 LCP 요소가 리소스 로드 완료 후 즉시 렌더링이 되도록 한다.

LCP 요소가 리소스 로드가 완료 후 즉시 렌더링이 되지 않는 주요 이유는 다른 이유로 렌더링이 차단되기 때문이다.

  • 의 스타일시트 또는 동기 스크립트가 아직 로드 중이라 렌더링이 차단된다.
  • 로드가 완료됐지만 LCP 요소가 아직 DOM에 추가되지 않았을때

해결 방법은 뭘까?

일단 렌더링을 차단하는 스타일시트를 줄이기 또는 인라인 처리를 한다.

스타일시트 크기가 커서 LCP의 리소스 로드 속도보다 느리게 되면 LCP 요소가 로드 완료되어도 스타일시트 로드가 끝나지 않아서 렌더링이 되지 않는다.

⇒ 스타일시트 크기 자체를 줄이던가, 스타일시트를 HTML에 인라인처리를 해라.

스타일시트 크기를 줄이려면 미사용 CSS 삭제, 중요하지 않은 CSS 지연, CSS 축소 및 압축을 하면 된다고 한다.


마침 내가 측정한 light house 결과에도 렌더링 차단 요청을 개선하라는 내용이 담겨 있다.

사용하지 않은 css 파일도 로드가 되어서 렌더링 차단이 되는 문제가 발생하고 있었다.


어떤 파일이 사용이 안 되고 있는지 확인하기 위해서 Chrome DevTools → Coverage 탭을 켜고 페이지를 로드하면 확인이 가능하다.

Usage Visualization에서 회색인 부분이 사용되지 않은 부분이라고 보면 된다.

많은 css 파일이 로드는 되는데 사용되질 않는다는 걸 확인할 수 있었다.

성능 최적화를 하지 않았을 땐 무려 21개라는 css 파일을 불러오고 있었다.

어떻게 해결했나?

그래서 나는 상호작용이 일어나야 보이는 컴포넌트들을 dynamic import로 불러와야겠다고 생각했다.

대표적으로 토글에 따라서 지도를 보여주거나 리스트를 보여주는 부분이 있다.

{viewMode === 'map' ? <Map /> : <PostList />}

그렇기 때문에 초기에 메인 화면에 진입하면 지도 영역만 보이지만 네트워크상으로 PostList 컴포넌트에 필요한 스타일도 화면에 보이지 않음에도 불러오는 것이다.

그래서 상단에 아래와 같은 코드를 추가했다.


const PlaceList = dynamic(
  () => import('../PlaceList'),
  { ssr: false },
);

nextjs/dynamic을 사용해서 동적 임포트를 원하는 컴포넌트를 해당 컴포넌트가 렌더링하는 시점에 JS Chunk를 비동기로 불러온다. 그러므로 초기 번들에는 포함되지 않고 리스트 화면이 렌더링 될 때 파일을 불러오도록 했다.

또 다른 사례가 있다.

지도 영역을 클릭하면 클릭한 위치를 기반으로 Popover 형태의 음식점 리스트가 보이게 된다. 평소에는 숨어 있다가 클릭해서 좌표가 있을 때 보인다.

그래서 원래는 해당 컴포넌트 내부에서 좌표가 존재함에 따라 Popover를 보여주는 형식으로 되어 있다.

function Page() {
	...
	return (
	  ...
		<PlaceDetailSheet />
	)
}

function PlaceDetailSheet() {
	...
	
	if(!coords) {
		return null;
	}
	
	return (...)
}

null을 return 하게 되어서 렌더링은 안 되는 게 맞지만 내부에 있는 import 파일은 초기 번들에 포함이 되었다는 거다.

그러므로 Page 컴포넌트에서 open 여부를 관리하여 조건부 렌더링이 가능하게 했다.

또한 dynamic import로 컴포넌트를 불러와서 초기 번들에 포함되지 않도록 했다.

const PlaceDetailSheet = dynamic(() => import('../PlaceDetailSheet')...);

function Page() {
	const [isOpenPlaceDetailSheet, setIsOpenPlaceDetailSheet] = useState("");
	...
	
	return (
		...
		{isOpenPlaceDetailSheet && <PlaceDetailSheet ... />}
	)
}

이렇게 처리하니 불러오는 css 파일 개수가 확실히 줄어들었다!

일단 이번엔 렌더링 차단을 하는 css 리소스를 줄이기 위한 최적화를 진행했다.

그 다음엔 서버에서의 응답 속도가 느려서 발생하는 Document request latency에 대해서 개선해 보도록 하겠다.

0개의 댓글