Next.js가 제공하는 강력한 기능 중 하나는 pre-rendering이다. 팀 프로젝트에 Next.js를 적용하기로 한 이유도, 블로그 서비스라는 프로젝트의 특성 상 SEO가 중요했기 때문에 Next.js의 pre-rendering 기능을 통해 SEO를 위한 SSR을 쉽게 하고자 했기 때문이다. 하지만 이 pre-rendering이 어떻게 일어나고 있는지 제대로 이해하지 못하면 예상 외의 순간에서 오류를 마주치기도 한다.
이번 글에서는 Next.js의 pre-rendering을 제대로 탐구하고, 이를 통해 프로젝트에 SSR을 적용한 경험을 소개하고자 한다.
Next.js의 공식 문서에 표현되어 있는 그림이다. React만 사용해서 만든 어플리케이션의 경우, 브라우저는 먼저 위와 같이 빈 껍데기인 HTML 파일을 받는데, 이후 Javascript 파일을 받고 실행하여 UI를 렌더링한다. 초기 렌더링이 사용자의 디바이스에서 일어나기 때문에 이를 client-side rendering이라고 한다.
따라서 자바스크립트 사용을 차단할 경우 아래와 같이 렌더링을 할 수 없게 된다.
Next.js에서 제공하는 pre-rendering은 아래와 같이 React 컴포넌트를 HTML로 생성해서 넘겨준다.
아래와 같은 간단한 코드를 Next.js를 사용해서 작성했을 때,
import { useState } from "react";
export default function Home() {
const [count, setCount] = useState(0);
return <div onClick={() => setCount(count + 1)}>카운트 값은 {count} 입니다.</div>;
}
자바스크립트 사용을 차단하더라도 아래 그림과 같이 렌더링 된 컴포넌트를 확인할 수 있다.
물론 자바스크립트 사용을 차단했기 때문에 클릭하더라도 카운트 값은 올라가지 않는다.
Pre-rendering은 수행되는 시점에 따라 다음과 같이 두 종류로 나뉜다.
getStaticProps
함수를 통해 SSG에 필요한 데이터를 fetch 해올 수 있다.hydration
이라고 한다.getServerSideProps
를 통해 SSR에 필요한 데이터를 fetch 해올 수 있다.❗ Next.js는 모든 페이지를 디폴트로 pre-rendering 한다.
더 정확히는, data fetching 없이 Static Generation을 통해 페이지를 pre-rendering 해 놓는다.
앞선 예시에 있던 코드의 경우 pre-rendering 시 data fetching이 필요없는 코드였고, 빌드 시점에 생성한 HTML 파일을 그대로 보내준 것이다.
Next.js는 알아본 것처럼 모든 페이지를 default로 pre-rendering 해 놓는다.
useEffect()
내부의 코드는 React에서 컴포넌트가 렌더링 된 이후에 브라우저에서 실행된다.프로젝트를 진행하면서 이를 제대로 인지하지 못하고 작업을 하던 중 오류를 마주한 적이 있다.
세션 스토리지를 사용한 useSessionStorage 훅을 만들 때의 일이었는데, 해당 훅에서는 초기값을 설정할 때 sessionStorage에 해당 상태가 저장되어 있는지 확인하고, 있다면 저장된 값으로 업데이트하고 없다면 초기값으로 설정해준다.
초깃값을 설정해주는 코드는 처음에는 아래와 같이 작성했었다.
const [value, setStateValue] = useState<T>((initialValue)=>{
const savedValue = sessionStorage.getItem(key);
return savedValue ? JSON.parse(savedValue) : initialValue;
);
🤔 하지만 이 훅을 적용했을 때 sessionStorage is not defined
라는 오류가 발생했다. 그 이유는 sessionStorage는 window의 프로퍼티인데, Next.js가 서버 사이드에서 위 코드를 실행할 때에는 window 객체가 없는 상태이기 때문이다.
따라서 아래와 같이 컴포넌트가 렌더링 된 이후 useEffect()
내부에서 세션스토리지를 확인해서 값을 바꿔주는 방식으로 구현하였다.
const useSessionStorage = <T>(key: string, initialValue: T) => {
const [value, setStateValue] = useState<T>(initialValue);
const [isValueSet, setIsValueSet] = useState(false);
useEffect(() => {
const savedValue = sessionStorage.getItem(key);
if (savedValue) setStateValue(JSON.parse(savedValue));
setIsValueSet(true);
}, []);
const setValue = (newValue: T) => {
setStateValue(newValue);
sessionStorage.setItem(key, JSON.stringify(newValue));
};
return { value, isValueSet, setValue };
};
Next.js가 pre-rendering을 디폴트로 수행하고 있기 때문에 window의 프로퍼티를 사용할 때는 이를 고려해서 코드를 작성해야 한다.
Next.js에서 SSR을 통해 SEO를 한 경험은 해당 링크에 포스팅하였다.
여기서는 간단히 소개하자면 SSR을 통해 meta tag를 작성할 수 있다.
getServerSideProps
로 article 데이터 받아오기export const getServerSideProps: GetServerSideProps = async (context) => {
const [bookId, articleId] = context.query.data as string[];
const article = await getArticleApi(articleId);
return { props: { article } };
};
getServerSideProps
는 서버에서 동작하는 코드이므로, 함수 내부에서 next/router를 사용할 수가 없다. 공식 문서에 따르면 context를 사용해야 한다. context 객체 내의 query에서 dynamic route의 query parameter를 가져올 수 있다.import Head from 'next/head';
interface ViewerHeadProps {
articleTitle: string;
articleContent: string;
}
export default function ViewerHead({ articleTitle, articleContent }: ViewerHeadProps) {
return (
<Head>
<title>{articleTitle}</title>
<meta name="description" content={articleContent.slice(0, 150)} />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta property="og:title" content={articleTitle} />
<meta property="og:description" content={articleContent.slice(0, 150)} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.knoticle.app" />
<meta property="og:image" content="https://kr.object.ncloudstorage.com/j027/knoticle.png" />
</Head>
);
}
그 외에도 SSR을 통해 동적으로 sitemap을 생성할 수 있다.
현재 프로젝트에서 cookie에 유저의 토큰 정보가 담겨있는데, 특정 데이터를 가져올 때 이 토큰을 통해 확인할 수 있는 유저의 아이디가 필요했다. 하지만 따로 설정해주지 않고 getServerSideProps 내에서 데이터를 가져오게 되면 쿠키에 아무것도 담기지 않기 때문에, 데이터를 적절히 가져올 수 없었다.
찾아보니 getServserSideProps
내에서도 cookie에 접근할 수 있는 방법이 있었다.
getServerSideProps
내에서 cookie 가져오기 아래 코드와 같이 context.req.cookies
로 가져올 수 있다. export const getServerSideProps: GetServerSideProps = async (context) => {
const [bookId, articleId] = context.query.data as string[];
const { access_token, refresh_token } = context.req.cookies;
const article = await getArticleApi(articleId);
const book = await getBookApi(bookId, { access_token, refresh_token });
return { props: { article, book } };
};
Syntax
Cookie: name=value; name2=value2; name3=value3
적용 예시
const response = await api({ url, method: 'GET', header: { Cookie: `access_token=${token.access_token}; refresh_token=${token.refresh_token}`} });
참고자료
Learn | Next.js
Cookie - HTTP | MDN
How to use cookie inside getServerSideProps
method in Next.js?