웹 어플리케이션 성능 측정 및 최적화 과정
프론트엔드 성능을 개선하면서 웹 사이트의 성능을 확인할 수 있는 도구를 검토하고 결과값을 측정하면서 발견한 개선 사항에 대해서 공유하고자 합니다.
웹 어플리케이션 성능 측정 및 최적화 과정

사용자에게 보여지는 화면을 개발하면서 최적의 사용자 경험을 제공하기 위해서는 프론트엔드 부분에서도 고민해야 하는 점이 많습니다.
이번에 프론트엔드 성능을 개선하면서 웹 사이트의 성능을 확인할 수 있는 도구를 검토하고 결과값을 측정하면서 발견한 개선 사항에 대해서 공유하고자 합니다.
웹 성능 측정 도구 : Lighthouse
웹 성능 측정 도구를 알아보면서 제일 먼저 접한 Lighthouse는 웹 페이지 성능에 대해 점수 지표를 제공하고 웹 페이지에 개선 필요한 부분에 대한 안내를 제공합니다.

Lighthouse 측정 예시
측정 항목 중 Performance는 화면에 콘텐츠가 얼마나 빨리 표시되고, 사용자는 얼마나 빠르게 화면을 볼 수 있는가에 초점이 맞춰져 있습니다.
First Contentful Paint (FCP) 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링 될 때까지의 시간을 측정합니다.
Speed Index (Speed Index) 페이지를 로드 중에 얼마나 빨리 콘텐츠가 시각적으로 표시되는지 속도를 측정합니다.
Largest Contentful Paint (LCP) 페이지가 처음으로 로드를 시작한 시점부터 가장 큰 이미지나 텍스트의 렌더링 시간을 측정합니다.
Time to Interactive (TTI) 사용자가 페이지와 상호작용이 가능하기까지의 시간을 측정합니다.
Total Blocking Time (TBT) 메인스레드가 응답을 하지 못할 정도로 오래 작업을 할 때 FCP와 TTI 사이의 시간을 측정합니다.
Cumulative Layout Shift (CLS) 사용자가 예상치 못한 레이아웃 이동을 경험(갑자기 바뀌는 부분이 생기는 등)하는 빈도를 수량화합니다.
이 6개의 메트릭을 바탕으로 점수를 산정하고 웹페이지의 개선이 필요한 부분에 대한 안내도 함께 제공하고 있습니다. 측정 결과로 개선 안내 사항에는 ‘너무 큰 페이로드’, ‘압축 필요’, ‘JavaScript 실행 시간’ 등이 있었고 이러한 부분이 성능 저하의 원인인 것을 알아냈습니다.
개선의 기준
다양한 네트워크가 제공되는 만큼 사용자들이 느끼는 웹 페이지의 체감 속도도 다양합니다. 일반적으로 ‘웹 사이트 로딩이 3초 이상 걸리면 사용자가 이탈한다.’ 라는 말도 있지만 그보다 웹사이트의 목적과 서비스의 유형에 따라 집중해야 하는 부분을 정하는 것이 합리적입니다.
제가 개발하고 있는 웹 사이트는 B2B 솔루션 이기 때문에 사용자의 3초 이후 사이트 이탈은 저희 솔루션에 중요한 요소는 아닙니다. 그러나 화면에서 작은 반응이라도 일어난다면 사용자는 무언가 진행되고 있는 것으로 이해하기 때문에 Lighthouse의 메트릭을 기반으로 가중치가 높은 LCP와 FCP에 초점을 맞추기로 결정했습니다.
웹 성능 측정 방법
Chrome의 개발자 도구는 웹 사이트 성능 측정 기능을 제공하고 있어, 그 중 Network와 Perfomance 탭을 사용하여 성능을 측정하였습니다.
— Network (리소스 측정)
크롬 개발자 도구를 켠 상태에서 페이지를 새로고침하면 HTML, CSS, JavaScript, 이미지, API 등 해당 페이지를 구성하는 모든 소스 코드에 대한 정보가 나타납니다.
제일 하단의 정보로 전체 파일의 개수와 시간, 그리고 크기를 확인할 수 있습니다.

Network 측정
— Performace
네트워크 탭에서는 어떤 리소스들을 어느 정도의 시간으로 받아오는지 확인할 수 있다면, 성능 탭에서는 시간을 기준으로 브라우저가 어떤 걸 화면에 그리는지 확인할 수 있습니다.
특정 시점을 클릭하면 어떤 작업에 많은 시간을 쓰는지, 초당 프레임 수나 CPU의 사용량 등도 자세히 들여다볼 수 있습니다.
이 중 Timings에 나오는 메트릭이 익숙해 보이실텐데요, 바로 Lighthouse에서도 평가하는 항목들이기 때문에 중점적으로 살펴봐야 하는 부분입니다.

Performance 측정
웹 성능 조치 방법
1. 번들 파일 확인 및 용량 줄이기
Webpack Bundle Analyzer라는 라이브러리를 활용하면 Webpack에 포함된 라이브러리들의 크기를 TreeMap으로 시각화하여 볼 수 있습니다.
프로젝트의 각 라이브러리가 얼마 정도의 크기로 사용되고 있는지 한눈에 파악할 수 있으며, 불필요한 라이브러리를 제거하면 용량이 어느 정도 줄어드는지 미리 확인할 수 있습니다.

Webpack Bundle Analyzer
— amCharts 라이브러리의 불필요한 라이브러리 제거
개발 중인 프로젝트에서 가장 많이 사용하고 있는 amCharts 라이브러리는 다양한 종류의 Chart를 제공하고 있습니다. 현재는 Chart를 이용하여 시각화하는 기능만 사용하고 있기 때문에 amCharts의 외부 라이브러리인 pdfmake.js, xlsx.js, canvg.js 라이브러리를 비활성화하여 Webpack의 크기를 줄입니다.
module.export = {
configureWebpack: {
externals: function (context, request, callback) {
if (/xlsx|canvg|pdfmake/.test(request)) {
return callback(null, 'commonjs ' + request);
}
callback();
}
}
}
— lodash 라이브러리 import 최적화
lodash 라이브러리는 함수 단위로 사용되고 있기 때문에, import 할 때 모든 함수를 가지고 오지 않도록 방식을 변경했습니다.
// Before
import { uniqBy, isEmpty } from 'lodash';
// After
import _uniqBy from 'lodash/uniqBy'; import _isEmpty from 'lodash/isEmpty';
또한, 사용하지 않는 lodash 코드를 제거해주는 lodash-webpack-plugin 라이브러리를 사용하였습니다.
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new LodashModuleReplacementPlugin()
]
}
}
babel-plugin-lodash 라이브러리를 설치하고 설정해주면 Webpack 번들링 시, lodash의 필요한 함수만 가지고 오도록 코드 변경 작업을 자동으로 해준다고 합니다.
— 경량화 라이브러리로 교체
현재는 codemirror의 application/json mode와 몇 가지의 옵션만 추가해서 사용하고 있기 때문에, 모든 개발 언어를 지원하거나 여러 개의 기능이 필요 없는 상황입니다. Webpack 번들 크기를 최적화하기 위해 mode와 addon을 경량화한 vue-codemirror-lite 라이브러리로 교체하였습니다.
2. Code Splitting
Vue CLI3부터 prefetch 기능을 기본값으로 제공하고 있습니다. prefetch 기능은 나중에 사용될 수 있는 리소스를 미리 캐시에 저장해두고 사용자가 웹사이트에 진입했을 때 렌더링 시간을 줄이기 위해 사용됩니다. 그러나 prefetch 기능을 모든 컴포넌트에 사용하게 되면 처음 렌더링 때 모든 리소스를 캐싱하기 위해 request 수가 증가하게 됩니다.
제가 개발하고 있는 웹사이트는 Component 단위로 구분하여 제작하고 있습니다. 예를 들어, Resource 페이지는 Resource.vue라는 Component 안에 세부 Component를 import 하는 방식으로 구성되어 있습니다.
사용자가 Resource라는 페이지에 진입했을 때, prefetch 기능으로 완전히 다른 페이지인 Dashboard의 Component까지 불러오는 작업은 불필요한 행위입니다. Code Splitting 기법을 사용하여 소스코드를 분할하고 처음 진입하는 페이지 외에 불필요한 소스 코드를 다운로드받지 않도록 코드를 수정합니다.
— prefetch 기능 제거
먼저, Vue에서 기본 기능으로 제공하는 prefetch 기능을 제거합니다.
module.exports = {
chainWebpack: config => { config.plugins.delete('prefetch'); }
}
— Vue Router Code Splitting
그리고 Vue Router에 페이지 별로 Code Splitting을 적용합니다. 화살표 함수를 사용하여 동적으로 import를 하는 과정입니다. webpack은 동일한 webpackCunkName을 가진 모든 컴포넌트를 동일한 모듈로 그룹화하여 빌드 되기 때문에 webpackCunkName 주석을 사용해야 합니다.
사실 Router 부분에는 이미 적용해두었지만, 알고 보니 Code Splitting이 제대로 적용되지 않았습니다. 바로 webpackChunkName을 사용하는 주석이 거추장스럽다고 생각하여 망설임 없이 삭제했기 때문입니다.
Webpack은 동일한 webpackCunkName을 가진 모든 컴포넌트를 동일한 모듈로 그룹화하여 빌드 되기 때문에 webpackCunkName 주석을 사용해야 합니다.
export default [
{ path: '/dashboard', name: 'Dashboard', component: () => import(/* webpackChunkName: 'Resource' */ '@/views/resource/Resource')
}
];
— Component Code Splitting
또한, 한 Component 안에서 사용되는 Component도 Code Splitting이 가능합니다.
<script>
const ResourceListDetail = () => import(/* webpackChunkName: 'Resource' */ './ResourceListDetail');
export default {
name: 'ResourceList',
components: {
ResourceListDetail
}
}
ResourceListDetail 컴포넌트는 ResourceList 컴포넌트에서 하나의 Row를 클릭했을 때 나타나는 Modal Component입니다. Resource 페이지에서 처음부터 필요한 Component가 아니기 때문에, 사용자가 Row를 클릭했을 때 나타나도록 Code Splitting을 적용할 수 있습니다.
이렇게 동적으로 import를 하게 되면 Resource 페이지에 진입했을 때 Resource 관련 소스 코드 파일만 불러올 수 있으며, webpackCunkName에 작성된 동일한 name으로 Webpack이 모듈을 분리합니다.
3. Webpack 최적화
Webpack에 compression-webpack-plugin과 Optimization 옵션을 추가로 적용하여 Webpack 최적화 작업을 진행합니다.
compression-webpack-plugin을 사용하여 gzip으로 압축해서 js, css, html 파일을 받는 시간을 단축시켜 성능을 개선합니다. 또한 작은 파일까지 압축하는 것은 시간이 소요되기 때문에 10240byte 보다 큰 파일만, 그리고 압축을 했을 때 비율이 0.8보다 작은 경우에만 압축하도록 설정합니다.
module.export = {
configureWebpack: {
plugins: [
new CompressionPlugin({
filename: '[path].gz[query]',
test: /\.(js|css)$/,
algorithm: 'gzip',
threshold: 10240,
minRatio: 0.8
})
]
}
}
큰 번들 파일을 분리하도록 Optimization 옵션을 사용했습니다. 그중 cacheGroups 옵션은 명시적으로 설정한 조건에 따라서 chunk 파일을 만드는 기능을 합니다.
해당 코드는 vendor라는 chunk group을 생성하고, 해당 그룹에 정적으로 import 된 모듈을 별도의 chunk로 만드는 작업을 진행합니다. chunk는 3가지 옵션을 제공하는데 async는 비동기 모듈에 대해서, initial은 정적 모듈에 대해서, all은 어떤 방식으로 import 되었는지 상관하지 않고 하나의 chunk로 만듭니다.
module.export = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
minChunks: 3,
priority: 2
}
}
}
}
}
}
또한, 자주 사용되는 모듈을 분리하여 캐싱 후에 재사용할 수 있도록 구성하였습니다.
여기서 name은 모듈의 이름으로 사용됩니다. chunkFileName을 [name].bundle.js로 설정하였으면 chartVendor.bundle.js로 모듈이 분리됩니다. test는 분리될 모듈을 정규식으로 작성합니다. 여러 모듈을 합쳐서 분리하는 것도 가능합니다. minChunk는 분할되는 chunk의 최소 개수이고, prioiry는 두 가지의 모듈에 속할 수 있을 때 가장 먼저 합쳐져야 하는 우선순위를 의미합니다.
마지막으로, Javascript와 CSS의 최적화를 위해 TerserPlugin과 CssMinimizerPlugin를 추가로 설치하였습니다. 자세한 내용과 설정 방법은 링크를 걸어둔 문서에 잘 정리되어 있습니다.
4. 불필요한 파일 정리
현재 프론트엔드 단에서 다국어를 제공하고 있습니다. 화면에서 필요한 문구뿐만 아니라 JSON 파일로 각종 컴플라이언스와 정책의 다국어를 제공하고 있는데요. 지원 언어 중 중국어는 더 이상 지원하지 않지만 파일이 삭제되지 않은 상태여서 번들링에 포함이 되어 바로 삭제를 진행하였습니다.
이 밖에도 평소에 ‘언젠가는 쓰겠지?’ 라는 마음으로 주석으로 처리해둔 JavaScript, CSS를 과감하게 정리했습니다.

다국어 파일 자체가 큰 용량을 차지하지 않을 것 같았는데 시각적으로 보니 눈에 띄게 차지하고 있었습니다. (다행스럽게도 해당 JSON 파일들은 API 호출을 통해 다국어를 지원하는 방향으로 변경될 예정입니다.)
다국어의 번들 사이즈를 확인하면서, 화면에서 보여지는 부분이라고 생각하여 모든 것을 프론트엔드에서 처리하는 것 또한 Webpack에 무리가 간다는 것을 느꼈습니다.
웹 성능 결과
1. Request 감소
Network 탭에서 캐시 비우기 및 강력 새로고침을 한 뒤 확인한 결과입니다. 성능 최적화 전에는 처음 진입한 페이지의 관련 파일만 불러오는 것이 아니라 다른 화면의 css와 js까지 모두 불러오며 요청 수가 300개에 육박합니다.

웹 성능 최적화 전
그러나 Code Splitting 후에는 요청 수가 약 30~40개 정도로 감소한 것을 확인할 수 있습니다.

웹 성능 최적화 후
또한, DOM 트리를 그리는데 걸리는 시간(DOMContentLoaded), DOM 트리를 포함하여 이미지까지 화면에 로드 되는 시간(Load)도 감소하였습니다.
2. LCP와 FCP 속도 변화
콘텐츠 렌더링의 속도 변화입니다. 최적화 전에는 FCP가 4.7초로 확인됩니다. 가장 큰 콘텐츠가 표시되는 시간인 LCP는 그보다 더 뒤인 7초대입니다.

웹 성능 최적화 전

웹 성능 최적화 후
웹 성능 최적화가 마무리 된 시점에는 FCP는 약 1.5초, LCP는 큰 차이로 감소한 것을 확인할 수 있습니다. Lighthouse에서는 좋은 사용자 경험을 제공하기 위해서 FCP가 1.8초 이하여야 한다고 합니다. 그러나 주로 Chart 라이브러리를 사용하고 그 Chart 안에서 무수히 많은 DOM Tree를 그려내는 것을 감안한다면, 1.5초의 감소도 큰 효과가 있다는 것을 알 수 있습니다.
Element 탭에서 body 태그를 삭제하고 빈 페이지인 상태에서 Performance 측정을 하면 더 정확한 결과를 확인할 수 있습니다.
마치며
아직 개선해야 할 부분이 많습니다.
Webpack Bundle Analyzer의 TreeMap을 보며, 저는 처음 봤지만 각 라이브러리들마다 관련이 있는 것을 확인했을 때는 라이브러리 도입에 신중해야 한다고 느꼈습니다.
Webpack 번들 사이즈를 줄이기 위해 많은 시도를 했지만 아직도 만족할 만큼 줄이지 못한 것을 보면 많이 갈 길이 구만리인 것 같습니다.
하지만 어설프게 적용했던 Code Splitting 기능을 다시 확인하고, 많은 request 개수를 감소시키는 과정을 통해 Webpack에 대해 조금 더 알 수 있었습니다.
짧은 시간이었지만 웹 성능은 애정 어린 관심과 시행착오로 충분히 개선 가능성이 있다는 것을 느꼈고, B2B 솔루션에서 더욱 중요한 성능 개선 포인트는 무엇일까 고민해 보며 이번 작업을 계기로 지속적으로 웹 성능을 개선하도록 하겠습니다.
도움이 된 사이트
메타데이터
- post_id
- 900ae9aecd0a
- slug
- 웹-어플리케이션-성능-측정-및-최적화-과정-900ae9aecd0a
- url
- https://medium.com/opsnow-security/%EC%9B%B9-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95-900ae9aecd0a
- canonical_url
- https://medium.com/opsnow-security/%EC%9B%B9-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95-900ae9aecd0a
- author_url
- https://medium.com/@jongyeon.kim
- status
- ok
- fetched_at
- 2026-08-03 09:43:36