성능 최적화에서 가장 중요한 것은 빌드라고 생각한다.
앱이 커질수록 그에 상응하는 코드가 늘어날 확률이 높아진다.
빌드 파일의 용량이 커질수록 앱의 속도는 저하하게 된다. 왜냐 앱을 실행할 때 빌드한 파일을 통신을 통해 받아오는데 파일 용량이 클수록 느릴 수 밖에 없다. 왜냐 통신을 통해 받아오는 파일의 크키가 커진다면 이 파일을 쪼개서 서버에서 보내준다. 파일을 쪼개는 시간과 이 쪼갠 파일을 다시 합치는 시간이 추가되기 때문에 파일의 용량이 커질수록 속도가 느려지는 것이다.
많은 프론트엔드 영역에서 사용하는 빌드 모듈은 webpack이다. webpack 사용법을 잘 알아야 빌드한 파일의 크기가 줄어들지 않을까?
webpack 4버전에서는 terser-webpack-plugin
이 포함되어 있지 않았다.
terser-webpack-plugin
은 terser
을 사용해서 자바스크립트를 축소한다.terser
는 자바스크립트를 파싱, 미니마이즈, 압축하는 툴킷이다.webpack 5버전으로 가면서 포함되므로 추가로 설치할 필요는 없다.
참고로 terser-webpack-plugin
에서 쓸만한 기능 중에 빌드할 때 console을 지우는 옵션도 있다.
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
},
},
}),
],
},
};
compression-webpack-plugin
을 활용해서 번들파일의 압축버전을 만들 수 있습니다. 이는 CloudFront에서도 가능하지만 CloudFront에서 하는 일이 많기에 webpack에서 하는 것이 더 좋은 방법이라고 생각했습니다.
이미지 포맷은 요즘 여러가지가 있습니다. 최근에 나온 webp, 기본 이미지 사이즈도 작아서 많이 사용합니다. 하지만 webp를 지원하지 않는 브라우저는 아직 존재합니다.
그래서 webp 이미지를 사용못할 경우에 대비해서 jpg 포맷도 추가해준다. 이를 HTML tag인 picture
태그에서 쉽게 적용할 수 있다. 그리고 뷰포트에 대한 이미지 사이즈도 정할 수 있다. 그러나 사이즈와 포맷이 많아진다면 source
태그가 많아질 수 밖에 없다. 그래서 컴포넌트를 만들어서 이미지를 보여주는 것이 좋다.
// before
<picture>
<source type="image/webp" media="(min-width:1920px)" srcSet={heroImage1920} />
<source type="image/webp" media="(min-width:1366px)" srcSet={heroImage1366} />
<source type="image/webp" media="(min-width:768px)" srcSet={heroImage768} />
<source type="image/jpg" media="(min-width:1920px)" srcSet={heroImageJpg1920} />
<source type="image/jpg" media="(min-width:1366px)" srcSet={heroImageJpg1366} />
<source type="image/jpg" media="(min-width:768px)" srcSet={heroImageJpg768} />
<img className={styles.heroImage} src={heroImageJpg1920} alt="hero image" />
</picture>
// after
<Picture imageName={'hero'} imageStyle={'style'} />
// Picture Component
const Picture = ({ imageName, imageStyle }) => {
return (
<picture>
<source type="image/webp" media="(min-width:1920px)" srcSet={`/public/assets/images/${imageName}-1920.webp`} />
<source type="image/webp" media="(min-width:1366px)" srcSet={`/public/assets/images/${imageName}-1366.webp`} />
<source type="image/webp" media="(min-width:768px)" srcSet={`/public/assets/images/${imageName}-768.webp`} />
<source type="image/jpg" media="(min-width:1920px)" srcSet={`/public/assets/images/${imageName}-1920.jpg`} />
<source type="image/jpg" media="(min-width:1366px)" srcSet={`/public/assets/images/${imageName}-1366.jpg`} />
<source type="image/jpg" media="(min-width:768px)" srcSet={`/public/assets/images/${imageName}-768.jpg`} />
<img className={imageStyle} src={`/public/assets/images/${imageName}-1920.jpg`} alt={`${imageName} image`} />
</picture>
)
import 구문 대신 scrSet에 경로로 집어넣게 되면 문제가 발생한다. fil-loader가 import로 가져온 이미지말고 img tag안에 경로로 넣은 이미지 파일들은 사용하지 않는것이라고 판단하고 빌드할 때 빌드에 포함하지 않는다.
이를 해결하기 위해 src 하위에 있는 assets 폴더를 public 폴더 하위로 옮기고 CopyWebpackPlugin을 추가하고
new CopyWebpackPlugin({
patterns: [{ from: './public', to: './public' }]
}),
public 폴더를 넣어주면 public에 있는 asset 폴더도 함께 빌드되기에 이미지 파일이 삭제되는 것을 방지할 수 있고 배포 후 이미지 경로 문제도 발생하지 않는다.
CloudFront에서 Lambda edge에서 이미지 resize하는 함수를 만들어서 query param으로 이미지 크기를 정할 수도 있다.
[AWS] CloudFront Lambda@edge 를 이용한 이미지 리사이징
HTML IMG의 srcset과 sizes 속성(updated)
stat
: 변환 전의 파일 크기이다. webpack에서 input 파일 사이즈이다.
parsed
: 플러그인등의 코드 축소 후 output 파일 사이즈이다.
gzip
: gzip 압축 후 파일 사이즈이다.
webpack-bundle-analyzer/README.md at master · webpack-contrib/webpack-bundle-analyzer
react 제공하는 lazy를 활용하여 lazy import를 통해 페이지별 리소스를 분리할 수 있다.
const Search = lazy(() => import('./pages/Search/Search'));
webpack에서 Tree Shaking을 하기 위해 package.json에 sideEffects : false
를 추가하면 Tree Shaking이 된다. 만약 Tree Shaking이 되면 안 되는 파일이 있다면 false 대신 배열에 파일 이름을 추가하면 해당 파일 제외하고 Tree Shaking이 된다. Tree Shaking은 사용하지 않는 파일을 빌드할 때 빌드 항목에서 빼는 것이다. 그래서 한 파일에서 하나의 함수를 import해서 사용하고 다른 수많은 함수를 사용하지 않는더라도 해당하는 파일은 Tree Shaking이 되지 않는다. 그래서 webpack의 Tree Shaking을 효과적으로 활용하려면 파일을 최대한 분리하는 것이 좋지만 프로덕션 코드에서 그렇게 분리하는 것은 오히려 프로그래밍하기 힘들뿐더러 Tree Shaking되는 파일의 용량도 미미하다. 그러므로 파일을 분리해서 Tree Shaking을 활용하는 것은 모듈에 해당하는 것이다
Building a Tree Shaking Friendly JavaScript Package
CloudFront 캐시 설정 (설정값, 해당 값을 설정한 이유 포함)
S3는 구글 드라이브라고 생각하면 편하다.
객체 : S3에 저장된 데이터 하나하나 (파일이라고 생각하면 됨)
버킷 : 객체가 파일이라면 버킷은 연관된 객체를 그룹핑한 최상위 디렉토리.
origin server : 원본 데이터를 가지고 있는 서버 보통 S3, EC2
edge server : 전세계에 퍼져있는 서버. 같은 요청에 대해 빠르게 응답해주기 위해 cache 기능을 제공
S3와 CloudFront의 flow는
S3에 파일들을 업로드할 수 있다. 이 파일들은 객체라고 불린다. S3에 업로드하면 S3는 CloudFront로 객체를 쿼리하고 CloudFront에서 사용자 컴퓨터로 객체를 쿼리한다. 그리고 CloudFront는 S3의 객체가 업데이트 되었는지 확인하기 위해 통신을 하는 구조이다.
CloudFront에서는 캐시와 응답 헤더를 정할 수 있다.
정적 사이트이기 때문에 TTL은 전부 1년으로 지정했습니다. 만약 사용자와 액션이 있는 사이트라면 캐시 정책은 제 맘대로 정하기는 힘들 것 같다. 캐시 정책은 유저가 사이트에 머문 시간, 액션 등 통계에 따라 결정되는 것이 옳다고 생각한다.
cache-control : max-age=31536000
브라우저마다 캐싱 시간이 다르기 때문에 cache-control 시간을 추가하였다. 그리고 정적 이미지는 max-age를 1년으로 설정했다. 기준은 lighthouse에서 정적 리소스의 캐시 만료 기간이 1년이상이 적절하다고 하는 것을 확인할 수 있다.
x-cache
캐시 o
캐시 o
캐시 x
No Cache-Control Header for files from AWS CloudFront with S3 Origin
Serve static assets with an efficient cache policy
react memo 활용
Layout Shift가 발생하지 않기 위해서는 reflow가 발생하지 않도록 지향해야 한다. 쌓임 맥락이 생성되는 조건에 따라 layout 변경을 top, left 속성이 아닌 transform의 translate 속성을 사용해서 변경해야 쌓임 맥락이 생성되어 reflow를 방지할 수 있다.
CustomCursor 변경은 top, left 가 아닌 transform.translate 속성을 활용한다.
검색결과 hover는 top 대신 transform translateY를 활용한다.
도움말패널은 right 대신 transfrom translateX를 활용한다.
결도 블로깅 열심히 했네