이 글은 웹 프론트엔드 개발 초보가 작성한 글로, 일부 잘못된 내용이 있을 수 있습니다.
이상하거나 잘못된 부분은 댓글을 통해 알려주세요!
필자는 현재 Friday라는 동아리에서 운영을 담당하고 있다.
그러던 어느 날 "우리 동아리만을 위한 블로그 페이지를 만들자!"라고 다짐하였고, 노션 API를 이용하여 노션에 글을 쓰면 사이트에서 글을 확인할 수 있는 구조로 제작하였다.
그러나 이 블로그는 큰 문제를 낳았는데...
우리 웹사이트가 느리다고?
그 문제는 바로 로딩 속도였다.
노션 API를 호출하는 시간이 너무 길어서, 길게는 3초 이상까지 길어지기도 했다.
심지어 동아리 구성원 중 한 분께서는 로딩을 기다리던 중 "아직 완성이 안됐나 보네요."라고 하셨다... 😱
어떻게 해결할 수 있을까?
노션과 관련된 API는 외부 API이기 때문에, 응답 속도를 개선하기 어려웠다.
이때 떠오른 것이 바로 "서버에서 미리 렌더링을 해주면 빠르지 않을까?" 라는 생각이었다.
이미 API 사용을 위해 순정 React에서 Next.js로 Migrate했기 때문에, Next.js의 다양한 렌더링 방법을 사용하면 해결할 수 있겠다고 생각했다.
컨텐츠 렌더링 방식
React를 이용하여 앱을 만들면 기본적으로 Client Side Rendering(CSR)을 사용한다.
CSR은 Build Time에 만들어진 js 파일을 클라이언트에서 불러와 직접 실행시킴으로써 렌더링을 진행한다.
이와 반대로 서버에서 렌더링을 하는 방식이 바로 Server Side Rendering(SSR)이다.
SSR은 서버에서 렌더링을 진행한 후, 클라이언트에게는 완성된 페이지(HTML)을 전달한다.
이 경우 검색 엔진에서 HTML의 내용을 확인할 수 있어, CSR보다 검색 노출에 유리하다.
CSR과 SSR의 공통점은 바로 매 요청마다 렌더링을 진행한다는 점이다.
자주 변화하는 컨텐츠를 불러오는 데는 유리하겠지만, 그렇지 않은 경우 사용자가 매번 렌더링 시간을 기다려야 하는 문제가 발생한다.
그렇다면 미리 렌더링을 해놓으면 안될까? Static Site Generation(SSG)를 사용하면 된다.
SSG는 Build Time에 미리 HTML 파일을 만들어 놓기 때문에 매우 빠른 응답 속도를 보여준다.
그러나, Build Time 이후의 변경 사항에는 전혀 대응하지 못하기 때문에 완전히 정적인 사이트에서 주로 사용한다.
Friday 블로그는 동적 컨텐츠에 대응할 수 있으면서도, 빠른 로딩 속도를 지녀야 했다.
SSR을 사용하면 속도가 느리고, SSG를 사용하면 새로운 블로그 글을 반영하지 못한다.
이를 위해 Next.js에서는 한 가지 방법을 더 지원해주는데, 바로 Incremental Static Rendering(ISR)이다.
ISR은 말 그대로 정적(Static)이면서 점진적(Incremental)이다.
SSG와 마찬가지로 Build Time에 렌더링을 진행하지만, 지정된 시간에 Revalidation을 진행한다.
ISR에서 개발자가 N초의 Revalidation 빈도를 지정하면, "사용자의 요청이 들어오고 N초 뒤에 백드라운드에서 정적 생성을 진행"한다.
첫 사용자는 예전의 화면을 보겠지만, N초 이후에 들어온 다른 사용자는 최신의 화면을 볼 수 있게 되는 것이다.
블로그와 같이 정보가 실시간으로 변동되지는 않지만 동적인 경우에 절충안으로 사용할 수 있다.
Next.js에서 ISR 적용하기
Next.js에서는 getStaticProps
를 사용해 ISR이나 SSG를 사용할 수 있다만, Next.js 13부터는 새로운 문법이 등장했으니 참고만 해두도록 하자.
import React from 'react';
import axios from 'axios';
// Next.js 13 이전 코드
export async function getStaticProps() {
const result = await axios.get('https://...');
return {
props: { apiResult: result.data },
revalidate: 10, // 10초마다 Revalidate. 이 속성이 없으면 SSG.
};
}
export default function MainPage({ apiResult }) {
return <div>{apiResult}</div>;
}
다른 접근: 체감 로딩 시간을 줄이자
체감 로딩 시간과 관련된 예시로는 엘리베이터 거울이 있다.
엘리베이터가 느리다는 민원을 "거울 설치를 통한 지루함 해결"으로 풀어냈다는 유명한 일화이다.
ISR/SSG를 사용해 실제 속도를 높였다면, 이제 체감 속도를 높여보자.
뼈대(Skeleton)
Skeleton은 사용자에게 "이 자리에 무언가가 로딩되고 있음"을 보여주는 훌륭한 도구이다.
Friday 웹사이트에는 블로그 목록에 뼈대를 활용하였는데, CSS의 mask
와 animation
을 활용해 애니메이션을 구현하였다.
블러 업(Blur Up)
Blur Up은 원본 이미지를 보여주기 전에 저화질의(Blur된) 이미지를 미리 보여주는 방법이다.
이를 사용하면 이미지가 갑자기 나타날 때 생기는 이질감과 레이아웃 변형을 없앨 수 있다.
특히 레이아웃 변형은 Cumulative Layout Shift(CLS)라고 불릴 정도로 악명이 높은데, 이는 사용자가 어떤 액션을 하려고 할 때 갑자기 이미지가 로딩되어 다른 버튼이 눌러지는 현상이 꽤나 빈번하기 때문이다. (다들 한 번씩은 경험해보셨죠?)
사실 Blur Up은 빠르게 적용할 수 있었는데, 바로 Next/Image
덕분이다.
Next.js에서는 자체 컴포넌트인 <Image />
를 활용해 다양한 이미지 최적화 기능을 사용할 수 있는데, 그 중 하나가 바로 Blur Up이다.
Blur Up 이외에도 Lazy Loading(화면에 보여질 때 이미지를 로딩하는 방법)이나 사이즈 최적화(화면에 보여질 크기에 맞는 사이즈의 이미지를 로딩) 등을 손쉽게 사용할 수 있다. (공식 문서를 참고)
import React from 'react';
import Image from 'next/image';
import profile from '../assets/images/profile.png';
export default function ProfileImage() {
return <Image
src={profile}
alt='Profile Image'
placeholder='blur' // Blur Up 사용
sizes="100px" // 크기 최적화를 위한 사이즈 지정
/>;
}
이 외에도 Friday 웹사이트에는 동영상에 미리 보기 이미지를 지정하는 등 다양한 방법으로 체감 로딩 시간을 줄이고 있다.
프론트 엔드는 다른 분야에 비해 들인 노력 만큼 결과물이 나오는 분야라고 생각한다.
아직 부족한 부분이 많지만, 시간이 될 때마다 Friday 웹사이트를 개선해나갈 생각이다.
이 글은 Friday Blog에서도 읽어보실 수 있습니다.
https://fridayproject.co.kr/post/7efffa5c-0c4a-4803-b227-57d04f4308c2