next 블로그 성능 최적화 진행기, 두번째
번들 사이즈 줄이기
번들의 사이즈를 줄이기 위해서는 우선 번들의 크기를 확인해야 합니다.
yarn add -D @next/bundle-analyzer // next.config.js const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }); const nextConfig = { reactStrictMode: true, images: { domains: ["s3.us-west-2.amazonaws.com", "res.cloudinary.com"], disableStaticImages: true, }, }; module.exports = withBundleAnalyzer(nextConfig);
그리고, build
명령어를 통해 확인해보면 다음과 같이 번들 사이즈를 확인할 수 있습니다.
코드 스플릿 하기
제 블로그 중 번들사이즈가 가장 큰 요소는 refractor
과, react-syntax-highlighter
이었습니다. 그런데 refractor
은 무엇인지 모르겠어서, node module 에서 찾아보았습니다.
노드 모듈을 확인해보니 이또한 react-syntax-highlighter
이었습니다. 사실 단순히 코드라인을 이쁘게 꾸미기 위한 라이브러리였는데 용량이 가장 크다고 생각하지도 못했습니다.
next
build 명령어를 통해 출력된 메시지와, 네트워크탭을 확인해보면 app-789e
로 시작하는 파일이 402Kb
크기를 가지며 가장 큰 것을 알 수 있습니다. 번들 애널라이저에서 확인해보면, refractor
의 압축 사이즈가 202Kb
인 것을 확인해보면 사실 저의 코드나, 다른 내용보다는 해당 라이브러리의 용량이 번들 사이즈에 가장 큰 영향을 끼치고 있었습니다.
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism"; const CodeBlock = ({ language, code }: { language: string; code: string }) => { return ( <SyntaxHighlighter language={language} style={vscDarkPlus} PreTag='div'> {code} </SyntaxHighlighter> ); }; export default CodeBlock; // react-syntax-highlighter은 여기서만 사용됩니다.
const DynamicCodeBlock = dynamic(() => import("./CodeBlock")); // 해당 컴포넌트를 dynamic import로 불러옵니다. const MarkDown = ({ markdownString }: { markdownString: string }) => { return ( <ReactMarkdown className='prose max-w-full select-text prose-li:marker:text-black dark:prose-invert dark:prose-li:marker:text-gray lg:mt-5 lg:max-w-[75%]' components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ""); return !inline && match ? ( <DynamicCodeBlock code={String(children).replace(/\n$/, "")} language={match[1]} /> ) : ( <code className={className} {...props}> {children} </code> ); }, }} remarkPlugins={[slug]} > {markdownString} </ReactMarkdown> ); };
진행 후 확인해보니, 다음과 같았습니다.
위와 비교해서 보면 first load js
의 크기가 확연히 줄었음을 확인할 수 있습니다. 이전 480kB
에서 242kB
로 절반정도 줄어들었습니다.
네트워크 탭에서의 초기 로딩때에도 최상단에 보이는 _app-fa8
파일의 크기가 절반이하로 줄어든 것을 확인해 볼 수 있습니다. 코드 스플릿을 통해 해당 라이브러리가 실제로 사용되는 post/slug
페이지에서 해당 라이브러리를 다운로드 하게 될 것입니다.
아무 post/slug
페이지에 들어가보면 아래처럼 새로운 js
파일을 다운로드 받습니다.
그중, 240kB
의 파일 안에 react-syntax-highlighter
이 들어가게 됩니다.
트리 쉐이킹
위에서 분석한 내용중, nodejs.html
을 확인해 보면 다음과 같이 출력됩니다.
무언가 이상한 부분이 있습니다. 사실 민망하지만, 여기 컴포넌트 중에는 제가 지금 사용하지 않고 있는 컴포넌트들도 다수 존재합니다. 위에 호버해둔 ProjectCard
컴포넌트는 새로 작업하는 페이지를 위해 사용하는 컴포넌트로 현재시점에 최종적인 pages
에서 사용되지 않습니다. 그런데, 번들에는 포함된것처럼 보입니다.
여기서 추론할 수 있는 점이, 한가지 있습니다. 사실 지금 트리쉐이킹이 잘 되고 있지 않은 게 아닐까요?
트리 쉐이킹은 사용되지 않는 코드를 제거하기 위해 JavaScript 컨텍스트에서 일반적으로 사용되는 용어입니다.
(ref: https://webpack.kr/guides/tree-shaking/ )
즉 트리쉐이킹이 잘 되었다면 사용하지 않는 코드들은 bundle
에 포함되지 않아야 합니다. 웹팩의 가이드를 보고 package.json
에 "sideEffects"
를 추가해주어야 했습니다. 일반적으로 다음과 같이 사용됩니다.
"sideEffects": false // 사용하고 있지 않은 export는 제거해도 괜찮다고 알립니다. { "name": "your-project", "sideEffects": ["./src/some-side-effectful-file.js"] } // 또는 이처럼 배열로 직접 알릴 수도 있습니다.
저는 간단히 "sideEffects": false 를 package.json
에 추가해주었습니다.
app
페이지로 접근 할때에 초기에 다운로드 되는 파일의 용량이 절반정도로 줄어들었을 확인할 수 있습니다.
다만 /post
경로에서는 위에 dynamic import로 설정한 react-syntax-highlighter
가 포함되어 용량이 조금 더 커지게 됩니다.
사용하지 않는 라이브러리 제거하기
번들 분석 결과를 세세하게 살표보니, 위의 pngjs
라이브러리가 무엇인지 궁금해졌습니다. 그리고 여기서부터 시작되는 아래까지의 라이브러리들을 합치만 꽤 큰 크기가 됩니다.
저는 gif-frames
라는 라이브러리로 gif
이미지를 base64
로 인코딩하는 유틸함수를 사용하고 있었고, 이 부분은 gif-frames
관련 부분처럼 보였습니다. 그런데, 저는 더 이상 해당 라이브러리를 사용하지 않고 있었습니다. 이미 모든 이미지는 webp
로 변환해 사용하고 있었기 때문입니다. 그래서 해당 라이브러리를 제거하고 사용하는 유틸함수를 제거했습니다.
해당 라이브러리를 제거하니, /post
페이지와 관련해 70kb
정도 용량을 줄일 수 있었습니다.
정리하기
제가 진행한 과정은 다음과 같았습니다.
- 초기 페이지에서 필요하지 않은 라이브러리를
dynamic import
로 lazy 하기. - 트리쉐이킹이 잘 되고 있는지 확인하기
- 사용하고 있지 않은 라이브러리 제거하기
이러한 과정을 통해 초기페이지 기준 480Kb
에서 84Kb
까지 번들의 사이즈를 줄일 수 있었습니다.