해당 포스팅은 매년 진행되는 카카오의 컨퍼런스인 if(kakao) 2021에서
Next.js와 Typescript를 이용한 프론트엔드 개발기 세션을 듣고 작성한 내용 정리 및 후기입니다.
Vercel에서 만든 Server Side Rendering
과 Client Side Rendering
을 동시에 지원하는 리액트 기반의 프레임워크
리액트 사에서도 서버사이드 렌더링 기술을 사용하고 싶다면 Next.js를 사용하라고 권장하고 있음.
Netfilx, Github, Uber, twitch 등 전세계 사람들이 사용하는 서비스에 적용될 정도로 성능, 안정성 측면에서 충분히 검증된 프레임워크이다.
동작방식
일반적으로 리액트에서 라우팅 기능을 사용하려면 react-router
라이브러리를 사용해야했음
Router 컴포넌트를 import하여, 라우팅 할 페이지들의 경로와 렌더링 할 컴포넌트를 props로 전달해야하는 번거로움이 있었다.
하지만, Next는 파일 시스템 기반 라우팅을 사용하고 있어 react-router
를 사용했을 때 생기는 번거로움이 사라진다.
Next는 빌드 과정에서 pages의 하위 디렉토리가 path로 설정되어 개발자가 라우팅에 대해 신경쓰지 않아도 되는 이점이 있다.
라우팅에는 크게 3가지의 방법이 있다.
pages/index.js -> /
pages/blog/first-post.js -> /blog/first-post
pages/blog/[slug].js -> /blog/:slug (/blog/hello-world)
코드가 커질수록 번들링 된 파일의 크기는 증가하게 되고, 초기 로딩 속도가 지연될 수 있다.
이에 대한 해결 방법으로는 필요에 따라 번들 파일을 여러 개로 분리하는 코드 스플리팅 전략이 있다.
일반적으로
를 사용하여 코드 스플리팅을 해왔음
하지만, Next.js는 Zero Config를 주장하고 있고, 개발자가 코드 스플리팅을 신경쓰지 않아도 된다.
Next.js는 빌드 과정에서 pages의 하위 디렉토리에 각 파일을 스플리팅하여 번들링하게 된다.
추가로, 공통으로 사용하고 있는 모듈들을 common 디렉토리 아래에 번들링하여 공통된 코드에 대한 전략적인 코드 스플리팅을 하고 있다.
ES2020에 추가된 dynamic import() 또한 지원하고 있다.
_app.js
와 _document.js
라는 기본 컴포넌트를 가지고 있음추가로 컴포넌트를 구성하는 과정에서 getInitialProps
가 정의되어있다면 API 서버로부터 데이터를 받아온 후, page props를 구성하게 된다.
Next.js 사용하여 개발 시 app 컴포넌트와 document 컴포넌트를 커스터마이징하는 것은 불가피함.
global CSS
를 정의하는 부분getInitialProps
를 정의할 경우 Next.js의 중요한 기능인 자동 정적 생성 기능이 동작하지 않음Html
, Head
, Main
, NextScript
컴포넌트를 리턴해줘야하는 규칙이 있음next/head
모듈의 Head
컴포넌트를 사용해야함Main
컴포넌트의 외부 컴포넌트는 브라우저에서 초기화되지 않기 때문에 팝업, 사이드바와 같은 공통 페이지에서 사용하는 컴포넌트는 반드시 app
컴포넌트에 작성해야함.이전 페이지의 스크롤이 유지되지 않는 상황은 어떻게 처리할까?
next/router
모듈로부터 Router
객체를 가져온 후 이벤트 메소드를 통해 routeChangeStart
이벤트에서 스크롤 정보를 캐싱하고 routeChangeComplete
이벤트에서 캐시된 스크롤 정보를 불러와서 페이지에 적용하는 방식으로 처리함
API Cache
popstate
를 이용하여 뒤로가기 여부를 판단하고 해당 Flag를 Store에 저장함
이 로직은 app 컴포넌트에 작성하여 모든 페이지에 적용될 수 있도록 함
page 컴포넌트 별로 정의한 getInitialProps
에서 popSelector
를 통해 스토어에 저장된 뒤로가기 Flag를 가져온 후 action을 dispatch할지 cached 된 데이터를 그대로 사용할지 여부를 판단하였음
Next.js는 Router 객체, Link 컴포넌트를 사용해서 원활한 경로 변경을 제공함
router.replace(..., ..., { shallow : true })
shallow
를 사용할 수 있음shallow
props를 사용할 수 있음<Link href="..." shallow>
shallow
옵션을 사용하면 getInitialProps
, getServerSideProps
메소드가 호출되지 않고 현재 상태를 유지한 채 경로 전환이 가능하다.tsconfig
와 next.config
를 typescript 확장자인 *.ts
, *.tsx
로 변경함*.ts
, *.tsx
로 변경함any
타입으로 지정함off
또는 warn
처리함Next.js는 Page 디렉터리 하위 리액트 컴포넌트로 Page를 구성하게 됨
카카오페이 구매에선 각 페이지 폴더 하위로 action
, reducer
, saga
를 구성하였었음
Pages
L path
L page.jsx
L store
L action.js
L reducer.js
L saga.js
Pages
L path
L page.tsx
L store
L action.ts
L reducer.ts
L saga.tsx
그런데 !!
Next.js에선 page 폴더 하위에 구성된 리액트 컴포넌트로 페이지를 구성하기 때문에 pages 폴더 하위에서 tsx 확장자를 가지는 파일은 반드시 리액트 컴포넌트를 export 해야함 ...
jsx 문법을 사용하기 위해 saga 파일을 페이지로 export하면.. 페이지로 구성되어버리는 문제가 발생.. ㅠ
따라서, 페이지를 구성하는 것이 아닌 컴포넌트(saga, action 등)는 따로 분리하는 것이 적절
Pages
L path
L page.tsx
Store
L path
L action.ts
L reducer.ts
L saga.ts
typescript 포팅 전략
포팅하며 현재 서비스 되고 있는 프로젝트 동작에 문제가 없어야 하기 때문에 문제가 발생하더라도 최소한으로 문제를 대처하기 위해 점진적으로 진행하도록 한다.
순차적 목표
1. config부터 빌드 성공까지 환경 설정을 완료하는 것 + jenkins 빌드 설정
2. API 모델의 Type 정의
3. Redux에 정의된 모델을 적용
4. Component 내 type 정의
Next.js에선 getInitialProps
보다 getStaticProps
와 getServerSideProps
사용을 권장하고 있음
getInitialProps
는 클라이언트와 서버에서 모두 동작하기 때문에 로직이 클라이언트와 서버로 나뉘어져 코드의 복잡도를 높일 수 있다.
정적 생성에서 사용하는 getStaticProps
와 서버사이드에서 동작하는 getServerSideProps
사용을 권장하고 있음
next/script
beforeInteractive
, afterInteractive
로 우선순위를 쉽게 설정하여 로드 성능을 향상시킬 수 있음next/image
재밌게 잘 들었습니다 :)