우리 서비스의 폴더구조를 살펴보니 Page Router로 되어있다.
그런데 내가 이전에 배웠던 AppRouter와 달라 그 차이를 알아보았다.
먼저 AppRouter는 Next 13버전에 나온 라우팅 방법이다.
라우팅 뿐 아니라 렌더링 방식 또한 다르다.
App Router는 app/ 폴더 기반으로
app/movie/page.jsx => /movie(o)
app/movie/common.jsx => /movie(x)
이처럼 App Router는 폴더 내에 page.js 혹은 router.js 만이 URL 경로에 자동 매핑된다.
그럼 어떤 점이 좋을까?
이렇게 되면 movie라는 폴더를 movie 컴포넌트만을 위해 집약적으로 사용할 수 있다.
Page Router와 비교하면 이해가 된다.
Page Router는 pages/ 폴더를 기반으로
pages/movie/page.jsx => movie/page
pages/movie/about.jsx => movie/about
pages/movie/index.jsx => movie
이처럼 Page Router는 index.js(or [id].js) 폴더 내의 파일명이 URL 경로에 자동 매핑된다.
이렇게 되면 pages/ 내부의 어떤 폴더든 URL 경로에 매핑 되기 때문에 대체적으로
pages 폴더 외부에서 해당 URL 경로를 렌더링하는 함수에 필요한 함수나 로직을 분리하여 작성할 수 있게 된다.
어떤 방식이 더 좋다라기 보다는 차이가 있고 폴더 구조가 편한대로 사용하면 된다.
App Router 는 기본적으로 server 에서 모든 걸 담당하는게 default다 (hydrate X)
대신 만약 client 코드 즉, hydrate가 필요한 (state,useEffect,useCallback 등) 코드는
'use client'를 파일 내에 명시하면 해당 파일은 html+js+css 로 먼저 보여지는 html 이 보내지고 이후 hydrate에 필요한 chunk.js 파일이 전달된다.
App Router는 serversiderendering이 기본값이다. 대신 use client를 사용하면 hydrate가 발생한다.
(use hydrate 라고 명시하게 해주시지 ㅎㅎ..)
라고 생각해도 좋다.
그럼 이런 오류를 예상할 수 있다.
use client가 없는 파일에서 useState나 useEffect 같은 함수를 사용하면 오류가 발생할까?
그렇다. 그렇기 때문에 저런 hook을 사용한다면 use client를 명시하고 클라이언트에서 React code를 수행하도록 해야한다.
그럼 데이터 페칭은 어떻게 이루어질까?
>App Router 방식에서는 컴포넌트는 async 하고 데이터 페칭을 await 할 수있다.
//CategoryListPage.tsx
export default async function CategoryListPage(){
const res= await fetch(`fetch URL`);
const data = await res.json()
return (
<div>
<h1>{data}</h1>
</div>
)
}
// App.tsx
function App(){
return (
<Suspense fallback={<Loading/>}>
<CategoryListPage/>
</Suspense>
)
}
이런식으로 컴포넌트를 return 하는 함수에 async를 씌워 Promise 객체를 반환하도록 하고
후에 Suspense를 통해 비동기를 핸들링 할 수 있다.
Page Router는 기본적으로 hydrate 된다고 보면된다. html+css+js로 만들어진 보여지는 html이 먼저 서버에서 만들어져 보내지고 차후에 hydrate에 필요한 chunk.js가 보내진다.
그럼 Page Router에서 serversiderendering은 불가능 할까?
사실 html+css+js를 미리 만들어 보내는 과정도 ssr이라고 볼 수 있지만
Next에서 말하는 SSR은 "데이터 페칭 이후 완벽한 HTML을 보내준다" 의 개념으로 인식할 수 있다.
그럼 Page Router에서 위와 같은 SSR을 어떻게 구현할 수 있을까
export const getServerSideProps = wrapper.getServerSideProps(({ dispatch }) => async context => {
try {
const res = await dispatch(fetchCategoryData()).unwrap();
return {
props: {
ssrCategoryList: res,
errorMessage: '',
},
};
} catch (err) {
return {
props: {
ssrCategoryList: [],
errorMessage: getErrorMessage(err),
errror: err,
},
};
}
});
하나의 예시로 "getServerSideProps" 를 사용하는 것이다.
컴포넌트 함수가 있는 곳에 해당 getServerSideProps를 저렇게 선언 후
사용자가 해당 페이지를 요청하면 그때 서버에서 데이터를 페칭한다.
return 된 props는 컴포넌트의 props 값으로 들어간다.
export default function CategoryListPage({ errorMessage, error, ssrCategoryList = [] }: Props) {
if (errorMessage) {
return (
<> //중략
</>
이런식으로 런타임에 데이터를 server에서부터 완전히 받아온 html을 받아오게 된다.
App Router => hydration이 default (O)
Page Router => hydration이 default (X)
이를 주의해서 사용하자 !