최근에 제가 Production NextJS 웹사이트의 성능을 개선해야 했습니다.
웹사이트는 여기에서 방문하실 수 있습니다 (변경 사항이 현재 적용되었습니다).
업데이트: 검색 엔진 결과 (24–08–24):
이러한 변경 사항을 발표한 후, 인덱스된 페이지 수가 79k에서 123k로 하루 만에 증가했습니다!
이것은 하루 만에 인덱스된 페이지 수에서 가장 큰 성과였습니다.
오늘은 우리가 어떻게 이를 달성했는지와 그 과정에 대해 설명드리겠습니다.
점수를 시작하겠습니다.
여기가 우리가 시작한 지점입니다.
공정한 비교를 위해 Vercel을 사용하여 테스트 계정에 배포하였으며, 이 계정을 모든 벤치마크에 사용할 예정입니다.
저의 초점은 기술적 개선뿐만 아니라 사용자 경험 개선에도 있습니다.
그럼 시작해 보겠습니다…
최적화 1: 병렬 요청
우리의 필터 페이지는 해당 지역에 적합한 필터를 얻기 위해 4개의 API 호출을 수행합니다.
하지만 여러분이 짐작하셨겠지만, 우리는 이러한 요청을 병렬로 처리하는 것을 막는 것은 없습니다.
단순히 모든 것을 Promise.all()
로 감싸는 것이었습니다.
이 최적화는 경험을 개선하였지만, 개선 효과에 대한 벤치마크는 하지 않았습니다.
최적화 2: 페이지의 정적 생성
저희 페이지는 getServerSideProps
를 사용하여 서버 측 렌더링되었습니다. 그러나 초기 페이지를 이미 예측할 수 있기 때문에, 제가 한 두 번째 작업은 이러한 페이지를 정적으로 생성하는 것이었습니다.
주요 이점은 빌드 시간 동안 이미 페이지가 생성되었기 때문에 사용자가 페이지를 보기 위해 버튼을 클릭할 때, 첫 번째 로드에서 위에서 논의한 4개의 API 호출을 할 필요가 없다는 점입니다.
이것은 점수를 개선하고 사용자 경험을 향상시킵니다.
최적화 3: 정적 페이지 미리 가져오기
저는 사용자 경험을 더욱 향상시키기 위해 한 걸음 더 나아가고 싶었습니다.
Next Link
컴포넌트를 사용하여 URL을 미리 가져올 수 있습니다.
그러나 우리가 논의하고 있는 페이지는 웹사이트의 여러 곳에서 참조되고 있으며, 사용자가 이 페이지들을 자주 방문하기를 원합니다.
그래서 저는 웹사이트의 어떤 페이지에든 사용자가 방문할 때마다 이 5-6개의 URL을 미리 가져오기로 결정했습니다.
이를 수행하는 방법은 PrefetchStaticPaths
라는 사용자 정의 컴포넌트를 만드는 것입니다.
const PrefetchStaticPaths: React.FC = () => {
const router = useRouter();
useEffect(() => {
const prefetchPaths = async () => {
const paths = [...] // 미리 가져오고 싶은 경로 목록
for (const path of paths) {
await router.prefetch(path);
}
};
prefetchPaths();
}, [router]);
return null;
};
그런 다음 이 컴포넌트를 _app.tsx
파일에 포함시켰습니다.
이는 사용자가 웹사이트를 방문할 때 페이지가 즉시 볼 준비가 되어 있음을 의미합니다. 방문하려고 할 때 거의 즉시 로드됩니다.
또한 위의 코드를 개선하여 이러한 요청을 병렬로 처리하도록 하였습니다.
수정된 버전은 다음과 같습니다.
const prefetchPromises = paths.map((path) => {
const startTime = performance.now();
console.log('미리 가져오기 시작:', path);
return router
.prefetch(path)
.then(() => {
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`미리 가져오기 완료: ${path}. 소요 시간: ${duration.toFixed(2)}ms`);
})
.catch((error) => {
console.error(`미리 가져오기 오류: ${path}`, error);
});
});
await Promise.all(prefetchPromises);
이것은 확실히 사용자 경험을 향상시켰지만 페이지 속도 점수에는 큰 영향을 미치지 않았습니다.
이미지는 페이지에서 가장 무거운 부분 중 하나입니다. 필터 페이지에서 여러 배너가 페이지 크기를 증가시켰습니다.
여기에서 TinyPng라는 멋진 무료 도구가 있습니다. 이미지를 끌어다 놓으면 비슷한 품질의 더 작은 크기의 이미지를 얻을 수 있습니다.
변환 후의 모습은 다음과 같습니다.
이 도구만으로도 이미지 크기가 65% 줄어들었습니다. 이는 상당한 차이입니다. 각 이미지마다 300kb를 절약하는 셈입니다.
더 나아가고 싶으신가요?
더욱 활용할 수 있는 다른 도구들도 있습니다. 예를 들어, 이미지 압축기를 사용할 수 있습니다.
이 도구는 색상 수를 지정할 수 있게 해주며, 이를 통해 이미지 크기를 추가로 50% 줄일 수 있습니다.
보통 PNG 이미지는 256색을 사용합니다. 그러나 8색을 사용하면 또 다른 50% 최적화된 이미지를 얻을 수 있습니다.
하지만 과도하게 사용하지 않도록 주의해야 합니다. 그렇지 않으면 시각적으로 품질이 저하될 수 있습니다.
어떤 진지한 프로덕션 애플리케이션에는 많은 추적 도구가 필요합니다. 저희 애플리케이션도 예외는 아닙니다. 저희는 Google Tag Manager와 몇 가지 다른 도구를 사용하고 있습니다.
NextJS는 이러한 스크립트를 지연 로딩하는 좋은 방법을 제공합니다.
스크립트 태그에 전략을 추가해야 합니다.
이 방법은 초기 로드 시간을 줄여줍니다.
최적화 6: 코드 분할 및 지연 로딩.
모든 것이 첫 번째 빌드에서 로드될 필요는 없습니다. yarn build
를 실행할 때 출력 결과를 검사하여 어떤 컴포넌트가 필요한 것보다 더 많은 항목을 로드하고 있는지 확인할 수 있습니다.
모든 사람이 공유하는 첫 번째 로드 JS라는 섹션이 있음을 알 수 있습니다.
이 리소스는 어떤 경우에도 로드됩니다.
좀 더 깊이 살펴보겠습니다.
이름을 확인하면 하나는 framework
이고, 다른 하나는 main
임을 알 수 있습니다. 나중에 확인하겠지만, 지금은 _app
으로 시작하는 파일에 집중해 주세요.
이 파일은 전체 프로젝트에서 공유되므로, 여기서 로드하는 모든 것은 어디에서나 로드됩니다. 따라서 여기서 모든 것이 올바른지 확인하는 것이 매우 중요합니다.
효율적으로 로드하는 한 가지 방법은 구성 요소를 동적으로 로드하는 것입니다. 스크립트를 포함해서요.
그래서 저는 다음과 같이 가져오기를 변환했습니다.
import BottomNavbar from '@/widgets/navigation/BottomNavBar';
를 다음과 같이 변환했습니다.
const BottomNavbar = dynamic(() => import('@/widgets/navigation/BottomNavBar'), { ssr: false });
그리고 모든 구성 요소에 대해 이렇게 했습니다.
이제 출력을 살펴보세요.
이 간단한 트릭을 사용하여 60kb를 줄였습니다. 나쁘지 않죠?
현재까지의 최종 결과
이 모든 작업을 한 후, 현재 점수는 다음과 같습니다.
그렇게 좋지는 않지만, 이제는 사용할 수 있죠, 맞죠?
더 나아질 수 있을까요?
네. 어떻게요?
잘 들어보세요.
가능성 1: 번들 분석하기
아주 유용한 도구가 있습니다. webpack bundle analyzer입니다. 이 도구를 NextJS 프로젝트와 연결하면 출력의 어떤 부분이 가장 많은 부하를 주는지 확인할 수 있습니다.
저는 이 도구를 실행해 보았고, 다음과 같은 결과를 보았습니다.
보시다시피, 저희는 lucide-react
라는 아이콘 라이브러리를 사용했습니다. 그리고 이것이 대부분의 공간을 차지하고 있습니다.
이를 대체할 맞춤 아이콘이 필요하며, 이를 관리하는 데 시간이 다소 소요될 것입니다. 그러므로 이는 다른 날에 진행할 예정입니다.
잠재적인 두 번째: 패키지 분석
프로젝트에서 라이브러리를 사용할 때, 우리는 종종 그 크기를 고려하지 않습니다.
또 다른 도구는 BundlePhobia입니다. 이 도구를 사용하면 패키지의 크기를 확인하고, 너무 무겁다면 대안을 고려할 수 있습니다.
저는 앞으로 며칠 안에 이것을 진행하여 얼마나 더 발전시킬 수 있을지 살펴보겠습니다.
마무리 발언
웹사이트 최적화는 지속적인 과정이며 끊임없는 노력입니다. 이 글에서는 우리가 활용할 수 있는 몇 가지 방법을 소개하려고 하였습니다. 물론 더 많은 방법이 있습니다.
그에 대해서는 다음에 이야기해보도록 하겠습니다.
읽어주셔서 감사합니다. 좋은 하루 되세요! 😀