Next.js
라는 멋진 리액트 프레임워크를 알게 된 후, 약 3주 동안 사이드 프로젝트를 진행하면서 느낀 점에 대해 간략히 정리해 보았다.
기존 리액트 프로젝트에서는 페이지 라우팅을 위해 react-router
라이브러리를 설치하고 거추장스러운 보일러 플레이트 코드들을 작성해야 했다.
하지만 Next.js에서는 그냥 페이지 컴포넌트로 사용할 파일을 pages
폴더 안에 그대로 넣어주기만 하면 자동으로 라우팅이 된다!
pages
경로 밑에 폴더를 만들면 해당 폴더 이름이 그대로 라우팅 경로에 사용되며, 동적 라우팅 또한 복잡한 로직 필요 없이 [id].tsx
형식의 파일 하나로 아주 간단하게 대신할 수 있다.
CRA로 생성한 리액트 프로젝트에서도 서버 사이드 렌더링을 적용할 수 있지만, 그 방법이 꽤나 복잡하고 까다로웠던 것으로 기억한다. (웹팩 설정을 여기저기 막 뜯어봐야 했던 걸로...)
하지만 Next.js에서는 따로 설정을 하지 않아도 기본적으로 서버 사이드 렌더링(또는 정적 생성)이 적용된다. 즉, 모든 페이지가 빌드 시 또는 요청 시 미리 렌더링되어 제공되기 때문에 높은 성능과 SEO 최적화의 이점을 누릴 수 있으며, 여기에 더해 CSR의 장점인 '새로고침 없이 자연스러운 페이지 이동' 또한 적용되어 있다.
(단, 이 모든 것이 너무 쉽게 되기 때문에 정확한 원리를 알기 어렵다는 단점이 될 수도 있겠다)
위 장점에 이어서 더욱 마음에 들었던 것은 서버 사이드 코드와 클라이언트 사이드 코드를 한 프로젝트 안에서, 같은 언어로, 심지어는 하나의 파일 안에 함께 작성할 수 있다는 것이었다.
기존에 풀스택 프로젝트를 진행할 때는 프론트엔드 코드와 백엔드 코드를 완전히 따로 작성한 후, API 엔드포인트도 만들어야 하고 프록시도 걸어야 하고 서버도 따로 띄워서 돌려야 했는데, Next.js는 이 두 가지를 혼자서 다 해낸다. 즉, 개발 편의성이 무척이나 좋았다.
예를 들면 페이지가 렌더링 되기 전 getStaticProps
및 getServerSideProps
등의 함수를 통해 DB 쿼리 등의 서버 사이드 코드를 먼저 실행한 뒤, 이것의 실행 결과물을 props
를 통해 클라이언트 사이드 쪽으로 넘겨주는 것이 가능하다. 직접적인 API 구현 필요 없이!
물론 클라이언트 쪽에서 API 호출이 필요하다면 이 역시 쉽게 가능하다. /pages/api
경로 밑에 서버 사이드 언어로 API 코드를 작성하면 해당 파일명을 엔드포인트로 하는 API 함수가 곧장 생성되기 때문이다.
반면, 단점도 약간은 존재했다.
핫 리로드는 코드의 일부분을 수정했을 때 전체 코드를 다시 컴파일하지 않고 바뀐 부분만 빠르게 고쳐서 실시간으로 변경 결과를 볼 수 있게 해주는 기능인데, 이것이 기존 리액트 프로젝트의 것보다 현저하게 느렸다.
리액트 프로젝트에서는 코드를 수정하고 저장 키를 누르면 거의 즉각적으로 변경사항을 볼 수 있었는데, Next.js에서는 2~3초는 빠른 편이고 심하면 10초가 넘어갈 때까지도 계속 로딩 중인 경우도 흔했다. 딱 CSS 한 줄만 바꿨는데도...
평소에 Ctrl + S키를 몇 초마다 계속 누르면서 결과물을 확인하는 습관이 있다보니 변경 사항을 신속하게 볼 수 없다는 점이 많이 답답하게 느껴졌다.
동적 라우팅 페이지에서 getStaticProps
으로 외부 데이터를 페칭하고자 할 때 반드시 함께 쓰여야 하는 getStaticPaths
함수는 빌드 시 미리 생성해 놓고자 하는 일부 동적 페이지 경로를 명시하는 역할을 한다.
이 함수는 배포 모드에서는 빌드 시에 딱 한 번만 호출되는 것과 달리, 개발 모드에서는 개발 편의를 위해 매 요청마다 호출되는데, 이것이 해당 동적 페이지의 응답을 매우 느리게 만든다.
반면 getServerSideProps
함수만 단독으로 이용하여 SSR 방식으로 동적 페이지를 구성하면 훨씬 빠르게 응답하는데, 이는 아마 단순히 DB 쿼리 요청 때문이 아니라 받아온 모든 경로에 상응하는 모든 페이지를 일일이 생성하는 과정이 추가되기 때문에 이렇게 시간 차이가 크게 나는 것 같다.
물론 설정을 통해 매 요청마다 수행되는 것을 방지할 수 있지만 아무래도 기본 성능이 개선되었으면 하는 마음이 있다.
이것은 위의 3번째 장점과 동일한 내용인데, 단점은 아니지만 개발 시에 약간 혼란이 생기기 쉬운 부분이라 넣었다.
예를 들어 DB에 쿼리를 보내 데이터를 가져와야 하는 상황이라면, 클라이언트 코드 영역에서는 API 호출을 통해, 서버 코드 영역에서는 API 호출이 아닌 직접 DB 쿼리 코드를 실행하여 데이터를 가져와야 한다.
클라이언트 사이드(주로 페이지 컴포넌트 내부)는 말 그대로 브라우저에서 실행되는 코드이므로 DB에 직접 접근할 수 없으며, 반대로 서버 사이드(getStaticProps
등)에서는 중복 수행을 방지하기 위해 API 호출 대신 직접 DB 코드를 실행하도록 권장되어 있다.
이를 개발 시 꼭 염두해야 하며, 양쪽에서 같은 기능을 써야한다면 API 함수와 실제 DB 접근 함수를 명확히 분리하여 관리하자. (헷갈리는 경우가 종종 있었다...)
이와 반대로 클라이언트 사이드에서만 접근 가능한 객체(localStorage
등)는 서버 사이드 렌더 과정에서 접근되지 않도록 useEffect
훅 내에서만 사용하도록 하자.