이전 포스팅에서는 nextjs가 fetch 함수를 이용해서 api로 부터 GET Request를 통해 어떤 data를 얻어온 값 또는 특정 계산이나 데이터를 읽어오는 함수와 같은 것들은 cached로 만들고 또 cached된 data를 update 하는 방법에 대해서 작성했다.
즉 비교적 작은 단위의 data를 cached로 만들고 update하는 내용이었다.
이번에는 그보다 조금 더 큰 단위인 page에 대해서 기록해두려고 한다.
먼저 nextjs를 npm run build
명령어를 실행시키면 위 사진과 같은 정보가 터미널창에 표시가 되는데 유심히 볼 필요가 있다!
Route (app) Size First Load JS
┌ ○ / 187 B 96.3 kB
├ ○ /_not-found 885 B 85.2 kB
├ ○ /chat 160 B 84.5 kB
├ ○ /create-account 2.23 kB 99.6 kB
├ λ /github/complete 0 B 0 B
├ ○ /github/start 0 B 0 B
├ ○ /life 160 B 84.5 kB
├ ○ /live 161 B 84.5 kB
├ ○ /login 2.16 kB 99.6 kB
├ ○ /products 1.08 kB 97.2 kB
├ λ /products/(...)products/[id] 692 B 90 kB
├ λ /products/[id] 187 B 96.3 kB
├ ○ /products/add 22.8 kB 107 kB
├ λ /profile 160 B 84.5 kB
├ ○ /sms 1.43 kB 90.8 kB
└ ○ /test 448 B 84.8 kB
+ First Load JS shared by all 84.3 kB
├ chunks/69-84289ba9e2c20ce0.js 29 kB
├ chunks/fd9d1056-5e221561fa023f51.js 53.4 kB
└ other shared chunks (total) 1.89 kB
ƒ Middleware 31.2 kB
○ (Static) prerendered as static content
λ (Dynamic) server-rendered on demand using Node.js
자세히 쳐다보면 각각의 page 앞에 표시된 기호들이 다 제각각인걸 확인할 수 있다.
그리고 조금 더 잘 쳐다보면 아래쪽에 O로 표시된건 Static 그리고 λ(람다)로 표시된건 Dynamic 이라고 설명까지 친절히 되어있다.
설명이 아주 명쾌하게 되어있기는 한데, 조금 다르게 설명하면 아래처럼 정리할 수 있을것 같다.
Static ➡️ 어떤 사용자던지간에 페이지에 표시되는것들이 바뀌지 않음. 따라서 page가 user의 dependency가 없고 위 설명과 같이 pre-rendered되기 때문에 nextjs에 의해서 단순한 html로 된다.
Dynamic ➡️ 사용자에 혹은 URL에 따라 페이지에 표시되는것들이 바뀜. 따라서 page가 user나 URL에 dependency가 있음. 예를 들어서 위에서 profile 페이지는 다이나믹 페이지인데 이는 cookies를 확인해 user마다 다른 페이지를 보여주기 때문임.
근데 여기서 아래와 같이 한가지 의문이 생길수 있다.
/profile 같이 Dynamic page라면, page를 보고있는 user에게 dependecy가 있는데, 그렇다면 DB가 원하는 값을 반환하기 전까지 user가 아무것도 못보는거아냐!?
하지만 그렇지 않다. 왜??
몇가지 방법으로 User에게 다른 화면을 보여줌과 동시에 뒤에서는 연산을 하거나 필요한 값을 받아올 수 있기 때문이다!
먼저 NextJS의 힘을 빌려서 loading.tsx를 사용하는 방법이다.
page.tsx 파일과 동일한 path에 loading.tsx 만들어서 사용하게되면 page.tsx에서 실제 연산을 위해서 얼마나 많은 시간이 걸린다고 한들 loading.tsx 파일을 통해서 나이스한 skeleton을 유저에게 보여줄 수 있다.
두번째 방법으로는 React의 Suspnese를 사용할 수 있다. (개인적으로는 nextjs사용할때는 loading.tsx를 더 선호한다)
사실 loading.tsx의 동작하는 방식과 큰 차이는 없다고 생각한다.
React Suspense를 사용하려면 profile.tsx를 예시로 들면 아래와 같이 사용할 수 있다.
user profile 정보를 갖고오는 부분을 async function으로 따로 분리하고 fallback에 loading 될때 보여주고 싶은걸 넣어주면 된다.
async function UserName() {
const user = await getUser();
return <h1>Welcome {user?.username}!</h1>
}
return (
<div>
<Suspense fallback={<h1>Loading!</h1>}>
<UserName />
</Suspense>
<form action={logout}>
<button>Log out</button>
</form>
</div>
)
코드를 위와같은 형태로 작성했다고 가정해보자.
그러면 Suspense의 힘으로 user가 해당 page에 도착하자마자 <form>
부분은 즉시 사용자가 볼 수 있고, UserName이 로딩이 될동안 Loading!
이라는 문구가 보여지고, 로딩이 끝났다면 Welcome!
이 보여지게 된다.
따라서 전체 페이지가 모두 로딩이 될때까지 user에게 빈화면을 보여줄 필요가 없다는 소리다.
즉 극한의 최적화와 빠른 성능을 위해서 모든 페이지를 static으로 만들어야할 필요는 없다는 생각이다.
필요에 따라서 Dynamic페이지가 필요할수도 있고 그럴때는 적절하게 스켈레톤이나 로딩 인디케이터를 통해서 유저에게 적당한 화면으로 응답해주면 된다고 생각한다.
Nope!
DB를 조회, 즉 외부로부터 데이터를 얻어오고 그 데이터를 렌더링한다고 해서 모든 page가 다 dynamic page는 아니다.
위에 npm run build
하고난 다음 각각의 page 정보를 보면 /products 페이지는 static으로 표시되어 있다.
전체 상품들을 화면에 보여줘야 하기 때문에 외부로부터 데이터를 얻어와서 화면에 렌더링하는데 왜 Static일까?
위에서 Dynamic Page는 user에 dependecny가 있는 page라고 했다.
이는 다시말하면 cookies, sessions, headeres, URL 등등을 기반으로 화면에 렌더링해야하는 데이터가 달라져야한다는걸 의미한다.
하지만 전체 상품의 경우는 쿠키도, 세션도, 헤더도, URL도 보지않고, 그저 DB를 조회할뿐이다 따라서 Static Page다.
즉 build할때 1회 DB를 조회하고, 조회한 결과를 기반으로 html을 만들기 때문에 static이라는 의미다.
바로 이전 포스팅에서 다뤘던 내용을 이럴때 써먹을 수 있다.
바로 revalidatePath를 이용해서 latest data를 기반으로 화면을 다시 렌더링할 수 있게된다.
따라서 revalidataPath를 이용해서 Data를 Update시키게 되면 해당 함수가 호출될때 DB를 다시 한번 조회하고 NextJS가 다시 html을 만들어서 우리에게 넘겨준다.
다른 방법으로는 export const revaliate = 60;
과 같은 형태로 page.tsx 상단에 적어주면 된다.
이는 unstable_cache / fetch request에서의 revalidat와 동일한 로직이다.
이전에 포스팅한 내용에 대해서 이해가 잘 되었다면 크게 어려움 없이 이해할 수 있는 로직이라고 생각한다.
다시한번 설명하면 60초가 지난 다음에 유저가 다시 해당 페이지에 돌아온다면 nextjs가 새로운 html을 만들어서 보여주겠다는 의미다.
하지만 유저는 항상 static page를 보게되는데 개쩌는 기능임!!!
즉 유저가 아무리 새로고침을 시도해도 DB를 다시 조회하거나 하는일이 없다!
그러면 User가 실제로 새로고침하고 DB를 다시 읽고싶게끔 하고싶다면 어찌하면 될까?
유저가 새로고침을 할때마다 데이터를 다시 얻어오게 하고싶다.
즉 실시간으로 데이터가 필요해서 Static이 아니라 Dynamic으로 만들고 싶다면, 아래와 같이 코드 1줄을 추가만 해주면 된다.
export const dynamic = "force-dynamic";
이렇게 작성한다음 npm run build를 실행해보면 실제로 static이라고 되어있던 페이지가 dynamic으로 표시되는걸 확인할 수 있다.
여기서 추가적으로 force-dynamic과 이전 포스팅했던 unstable_cache를 조합해서 사용할수도 있다.
즉 data는 실시간으로 유지를하되, 화면에 보여지는 일부 데이터는 cached로 만들어서 꼭 실시간 데이터가 필요하지 않은 부분은 cached로 만들어서 사용이 가능하다.
예를 들어서 주식이나 가상화폐를 거래한다고 생각할때 각각의 종목들의 가격, 호가, 틱들은 실시간으로 보여주되 프로파일 정보의 이름, 번호, 거래이력 등등은 cached된 데이터를 사용해서 data를 gathering 하는 resource를 좀 더 최적화가 가능하다!