-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[성능 베이스캠프] 지그(송지은) 미션 제출합니다. #10
Conversation
- change push branch - add s3 run command
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인덴트 다 줄인 건데 왜 늘어났는지 모를 일이네요 😭
<div className={styles.featureItem}> | ||
<video className={styles.featureImage} autoPlay muted loop> | ||
<source src={videoSrc} type="video/mp4" /> | ||
Sorry, your browser doesn't support embedded videos. | ||
</video> | ||
<div className={styles.featureTitleBg}></div> | ||
<h4 className={styles.featureTitle}>{title}</h4> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gif
를 mp4
로 변경
src/components/Footer/Footer.jsx
Outdated
return ( | ||
<footer> | ||
<p>memegle 2021. All rights reserved. Powered by <a href="https://giphy.com/">Giphy</a>.</p> | ||
</footer> | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오잉 이건 인덴트를 4 → 2로 줄인건데 왜 더 들어간 것처럼 됐지
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인덴트가 space 2칸이 아니라 tab 2칸인 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헉 글쿠나 고마워용
export default React.memo(GifItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GifItem memoization
@@ -41,6 +41,6 @@ | |||
/* PC */ | |||
@media all and (min-width:1080px) { | |||
.gifItem:hover { | |||
top: -0.75rem; | |||
transform: translateY(-0.75rem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
layout shift 방지
src/components/NavBar/NavBar.jsx
Outdated
<span className={styles.logo}>memegle</span> | ||
</Link> | ||
<Link to="/search"> | ||
<button className={styles.searchPageButton} onMouseOver={() => SearchLoadable.preload()}>start search</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Search 컴포넌트에 loadable preload 적용
src/pages/Search/Search.jsx
Outdated
<span>We Found...</span> | ||
</h4>; | ||
if (noResult) { | ||
return <h4 className={styles.resultTitle}><span>Nothing</span>🥲</h4>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
노팅 아니고 낫띵!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
src/service/fetchGif.js
Outdated
const cache = { current: null }; | ||
|
||
return async () => { | ||
try { | ||
if (!cache.current) { | ||
const response = await fetch(TRENDING_GIF_API); | ||
const gifs = await response.json(); | ||
const data = formatResponse(gifs.data ?? []); | ||
|
||
cache.current = data; | ||
} | ||
|
||
return cache.current ?? []; | ||
} catch (error) { | ||
return []; | ||
} | ||
}; | ||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클로저 리턴하여 객체(cache
)값 지키기!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const cache = { current: null };
과 같이 작성한 이유가 있는지 궁금합니다(순수한 호기심)
useRef
와 비슷하게 사용된다는 뉘앙스를 주기 위해서일까요~?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bigsaigon333
사실 @Tanney-102 브콜의 코드가 좋아보여서 참고했습니다! 다르게 하려고 했는데 current
라는 네이밍이 탐나더라구요 ㅋㅋㅋ
저는 useRef
와 비슷한 뉘앙스라고 생각하고 사용했습니다 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bigsaigon333 @zigsong 덧붙이자면 추후 코드를 고도화할때 추가 정보를 넣을 수 있겠다 생각했습니다 (최초 캐시 시간, 캐싱 기간 등) 여러 변수로 관리하기 보다 하나의 객체로 관리하는게 좋다 생각했어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클로저를 잘 적용하신 것 같습니다 👍👍👍 멋지네요 ㅎㅎ
현재는 fetchTrendingGifs
에만 캐싱이 적용되어 있는데 만약 다른 api 요청에도 캐싱이 적용되야 한다면 어떨까요?
한단계 더 추상화하여 캐싱이 적용된 함수를 리턴하는 함수가 있다면 엄청 fancy 할 것 같습니다 😄
webpack.config.js
Outdated
filename: '[name].js', | ||
path: path.join(__dirname, '/dist'), | ||
clean: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code splitting용 청크 분리
process.env.GIPHY_API_KEY | ||
? new DefinePlugin({ | ||
'process.env.GIPHY_API_KEY': JSON.stringify(process.env.GIPHY_API_KEY), | ||
}) | ||
: new Dotenv(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
리뷰어 곤이가 알려준 환경변수 정의하기
minimizer: [new CssMinimizerPlugin(), new TerserPlugin()], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optimize 켜주기! (true
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 미처 신경쓰지 못하고 넘어간 부분들(max-age, CSS chunk 분리) 등을 잘 적용해주셔서 많이 배울 수 있었습니다 👍
성능최적화에 대해 정리하신 블로그 글도 엄청 깔끔하게 잘 쓰셨더라고요. 지그의 정리 실력을 닮고 싶네요 😄
그런데 vscode의 인덴트 설정이 잘 못 되어 있는지 코드의 인덴트가 엄청 깊었습니다 ㅎㅎ;;
한번 확인해주시면 좋을 것 같아요...!
추가적으로 남긴 코멘트도 한번 고민해보시면 좋을 것 같습니다 👍
src/components/NavBar/NavBar.jsx
Outdated
<Link to="/search"> | ||
<button className={styles.searchPageButton} onMouseOver={() => SearchLoadable.preload()}>start search</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onMouseOver={() => SearchLoadable.preload()}
이 부분은 익명함수를 안 써도 괜찮을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
익명함수를 안쓰면 이벤트 객체가 preload
의 매개변수로 전달되지 않나요..? 👀
(Loadable Components 안써봐서 걍 궁금해서 물어봅니다 🐕 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preload에서 인자를 쓰지 않으니 이벤트 객체가 넘어가도 상관없다고 판단했습니다 ㅎㅎ
src/pages/Home/Home.jsx
Outdated
</div> | ||
<Link to="/search" onMouseOver={() => SearchLoadable.preload()}><button className={styles.cta}>start search</button></Link> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 마찬가지로 익명함수 없이 해결할 수 있을 것 같습니다!
const SearchLoadable = loadable(() => import("../Search/Search"), { | ||
fallback: <div>Loading...</div> | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fallback을 주셨군요!
fallback이 뜨는 상황일 때, 지금의 코드에서는 일시적으로 흰색 배경이 뜰 것 같아요. memegle의 기본적인 배경색이 검은색이니까 사용자 입장에서는 눈이 조금 아플 것 같습니다 😂
지그도 이를 잘 알고 대비책으로 마우스 엔터시에 프리로드를 주셔서 해결하셨네요 👍
하지만 사용자가 tab 키를 이용하여 start review 버튼을 누른다면 깜빡이는 현상이 발생하지 않을까요..? 🤔
src/service/fetchGif.js
Outdated
const cache = { current: null }; | ||
|
||
return async () => { | ||
try { | ||
if (!cache.current) { | ||
const response = await fetch(TRENDING_GIF_API); | ||
const gifs = await response.json(); | ||
const data = formatResponse(gifs.data ?? []); | ||
|
||
cache.current = data; | ||
} | ||
|
||
return cache.current ?? []; | ||
} catch (error) { | ||
return []; | ||
} | ||
}; | ||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클로저를 잘 적용하신 것 같습니다 👍👍👍 멋지네요 ㅎㅎ
현재는 fetchTrendingGifs
에만 캐싱이 적용되어 있는데 만약 다른 api 요청에도 캐싱이 적용되야 한다면 어떨까요?
한단계 더 추상화하여 캐싱이 적용된 함수를 리턴하는 함수가 있다면 엄청 fancy 할 것 같습니다 😄
src/pages/Search/Search.jsx
Outdated
useEffect(async () => { | ||
if (loading) { | ||
const gifs = await fetchTrendingGifs(); | ||
|
||
setGifList(gifs); | ||
setLoading(false); | ||
} | ||
|
||
return () => setLoading(true); | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 놓치고 있던 부분인데 공원의 함정이 있었네요 ㅋㅋㅋㅋ
도비 덕에 알았습니다. useEffect의 콜백이 async 함수였네요 ㅎㅎ;;;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오마이갓
넵 지그 리뷰 반영해주신 것 확인했습니다! 바로 merge 하겠습니다! |
🔥 결과
배포한 CloudFront 접근 경로: https://d3sydo67idcl5c.cloudfront.net/
개선 전후 성능 측정 결과
개선 전 (S3)
개선 후 (CloudFront)
Lighthouse 95점 이상 → 위 사진
Home 페이지에서 불러오는 스크립트 리소스 크기 < 60kb
히어로 이미지 크기 < 120kb
프랑스 파리에서 Fast 3G 환경으로 접속했을 때
✅ 개선 작업 목록
1 요청 크기 줄이기
소스코드 크기 줄이기
gzip
압축: 797kB → 137kBCssMinimizerWebpackPlugin
+MiniCssExtractPlugin
TerserWebpackPlugin
사용 (UglifyWebpackPlugin
과 압축 후 사이즈는 거의 일치하지만, 파싱 속도가 더 빠르다고 한다.)이미지 크기 줄이기
png
→webp
,gif
→mp4
2 필요한 것만 요청하기
loadable components의 prefetch 기능 사용
3 같은 건 매번 새로 요청하지 않기
Cache-Control: max-age
헤더 추가4 최소한의 변경만 일으키기
React.memo
로 래핑<memoize 이전> 모든 GifItem이 리렌더링
<memoize 이후> 기존의 GifItem은 리렌더링이 되지 않음
top
속성 대신transform: translate
사용🧐 공유
ImageMinifierPlugin
은 왜 compression이 거의 되지 않을까? 어떤 옵션과 함께 사용해야 할까?Terser vs. Uglify vs. babel-minify: Comparing JavaScript minifiers - LogRocket Blog
MiniCssExtractPlugin
을 사용하여 CSS 파일을 분리하는 것이 모든 경우 더 바람직할까?webp
는 상당히 혁신적인 확장자인 것 같다.gif
를 지양해야 하는 이유에 대해서도 아티클을 제대로 읽어봐야겠다.💎 성능 미션 정리 블로그