Next.js 캐럿마켓 클론코딩( CACHING 1)

짜스의 하루 ·2024년 8월 21일

caching을 해야 하는 이유?

"데이터를 caching한다"

우리가 서버 컴포넌트에서 데이터를 fetch할 때마다 (api를 request 한다고 가정해보자) requset는 cache될 것이다.

이게 무슨 소리일까? -> user들이 이 URL를 처음 fetch할 때, (우리의 서버가 이 URL을 처음으로 fetch할 때) API는 실제로 호출이 이루어지고, response는 NEXT.JS에 의해서 메모리에 저장이 될 것이다.

따라서 user가 두번째로 같은 URL로부터 fetch할 때, NEXT.JS는 그 API를 호출하지 않고, 대신 이전에 response를 user에게 줄 것이다(메모리에 저장되어있던 것, 즉 cache에 저장되어 있던 것을 의미함)

api fetch에서부터 오는것은 자동으로 cache가 이루어진다.
그래서 우리는 데이터베이스에서 오는 것을을 cache하는 법을 배울 것이다 -> 데이터베이스와 상호작용하는 시점과 횟수를 효율적이고 주의깊게 처리해야 하기 때문이다!

만약, 이전에 한번 데이터 접근이 완료되었을 때, 두번째 데이터에 접근할 때, 데이터베이스에 접근하는 대신에 우리는 이전 query에서 받았던 데이터를 return 하고 싶은 것이다
-- > 다시 새롭게 접근하지 않는다. 단지 메모리에 데이터를 return 할 뿐이다.


우리는 현재 RootLayout에
metadata title 속성 중에 template에 '%s | karrot Market'으로 속성을 지정해 두었었다. 여기서 %s는 수동으로 변할 수 있는 값을 의미한다.
이렇게 metadata = { }에 title 속성을 지정해주면, Home이 %s 속성에 들어가서 Home | Karrot Market이 된다!

그럼 products/[id]로 접근할 때에는, title | Karrot Market으로 되면 좋을 것 같다.

여기서 generateMetadata는 Next.js 13의 새로운 기능으로, 동적 라우팅을 사용하는 페이지의 메타데이터를 동적으로 생성하는 데 사용되는 기능이다.

generateMetadata에서 params를 받아오고, 받아온 params.id를 이용해 getProduct를 부른 뒤, product.title을 title로 설정하고 있다. 이렇게 해당 id의 title에 따라 동적으로 title이 변경되는 것을 확인할 수 있다.
현재 db에 저장되어있는 product를 가져오는 getProduct()를 정의해 두었는데, 이 함수가 generateMetadata에서 title을 가져올 때 한 번 부르고, 또 UI를 꾸미기 위해 한번 즉, [id]/page.tsx에서만 두번 호출이 이루어지고 있다.

어떻게 보면, 똑같은 함수(데이터를) 두번 부르고 있는 것이기 때문에 되게 비효율적이라고 생각할 수 있다.
여기서, 중요한 점은 -> 이제 cache에 저장된 데이터를 불러올 것이라는 것이다!


unstable_cache

unstable_cache라고 Next JS에서 제공해주는 함수로, 데이터를 캐시하여 성능을 최적화하는 데 사용된다.
이 함수는 주로 데이터 fetch 함수에서 사용되며, 서버 컴포넌트나 API에서 반복적으로 데이터를 가져올 때 동일한 데이터를 매번 새로 가져오지 않고 캐시된 데이터를 사용할 수 있도록 도와준다.

우리 데이터베이스에서 오는 응답을 cache해주기 위해서 우리는 unstable_cache라는 함수를 사용할 것이다.

우리의 데이터에 cache를 넣어줄 때, cache를 거대한 object라고 생각하면 된다 -> 우리는 cache에게 이름을 붙여줘야 하는 것이다.
cache에는 함수에서 return 되는 것이 무엇이든 내가 지정한 이름으로 저장이 된다.

unstable_cache 를 불러오지만, as를 사용해서 닉네임을 nextCache로 지정해 주었다.

const getCachedProducts = nextCache(getInitialProducts, ['products']);

첫번째 arguments는 비용이 많이 드는 계산이나 데이터베이스 query를 가동시켜줄 수 있는 함수를 적어주어야 한다.
두번째 arguments는 keyParts를 작성해 주어야 하는데, return 하는 데이터를 cache안에서 식별할 수 있도록 이름을 붙여주는 것이라고 생각하면 된다.

producst/page.tsx는 getInitialProducts()함수로 모든 products의 정보를 가져오는데, 이 함수를 getCachedProducts의 첫번째 argument로 지정해 주면서,

데이터를 불러올 때, getCachedProducts를 불러와서 사용하면 된다.

getInitialProducts 를 대신해서 getCachedProducts를 사용하게 되면, getCachedProducts는 일단 NextJS cache로 가서 ['products'] 에 대한 데이터를 찾으려고 할 것이다.

단, 처음에는 찾을 수 없다(저장된 데이터가 없기 때문) -> getInitialProducts에 접근하고, 다수의 제품들을 return 할 것이다.
--> 그런 다음에 NEXT JS Cache가 우리에게 getCachedProducts에 있는 모든 제품을 주게 된다. 이 함수가 두번째 실행될 때에는 cache로 가서 ['products']를 찾아보게 된다.

이렇게 getInitialProducts()에 console.log('hit')를 찍어보았다. cache를 사용하기 전에는 page를 새로고침할 때마다 데이터를 새로 불러오기 때문에 hit이 계속 찍히게 될 것이다.

하지만, ['products']에 저장되었기 때문에, 여러번 새로고침을 하더라도, 한번만 호출되는 것을 확인할 수 있다

하지만, cache는 우리가 서버를 중지한다거나, 파일을 수정한다거나 다시 서버를 시작한다고 하면, cache는 사라지게 된다.


revalidate

데이터를 어떻게 갱신하는지, 데이터를 어떻게 다시 새로고침 해주는지를 배워볼 차례이다.
위와 같이만 진행한다면, 만약 user가 제품을 업로드 하면, 그 제품은 홈 화면에 보이지 않게 된다. --> 새로운 데이터가 있을 때 user에게 최신 데이터를 보여주고 싶을 수 있기 때문에 새로고침이 이루어질 수 있어야 한다.

nextCache의 요점은 데이터가 같을 시에는 데이터베이스에 접근하지 않는다는 점이다. --> 데이터가 변경되지 않았을 시 말하는 것,

하지만 새로운 데이터가 있다면, 우리는 그 cache를 새로고침을 할 방법을 찾아야 한다.
--> user가 최신 데이터를 볼 수 있을 뿐만 아니라 새로운 데이터가 없을 땐 user가 데이터베이스에 접근하지 않아도 되도록 말이다.

총 3가지가 존재하고 첫 번째 -> revalidate(갱신)을 배워볼 것이다.

nextCache()의 3번째 인자로 들어가게 되는데,

getInitialProducts함수가 처음으로 호출되는 순간, 이 시계(60초) 가 작동하기 시작한다.

그리고 만약에 user가 페이지를 다시 요청하는데, 60초가 지나지 않았다면, nextCache는 cache안에 있는 데이터를 return하게 된다.

만약, 60초가 지났는데도 페이지를 다시 요청하지 않는다면, cache안에 있는 데이터가 "최신이 아니다" 라고 간주하게 된다.
-> nextJs는 최신 정보를 불러오기 위해서 getInitialProducts를 다시 호출하게 되고, 시계는 다시 작동하게 된다.

revaildate : 60 -> 언제 cache안에 데이터가 너무 오래됐다고 간주되는지, 새로고침을 할 필요로 하는지에 대한 것을 의미한다.

임의로 데이터베이스에서 상품의 title를 수정했다고 가정해보자

revalidate: 60로 설정 -> 60초마다 캐시를 갱신하도록 설정한 것이다.
캐시가 만료되기 전까지는 기존의 캐시된 데이터를 반환하며, 만료된 이후에 요청이 들어오면 새로운 데이터를 가져와 캐시를 갱신한다.


revalidatePath

우리가 요청했을 때, 데이터를 새로고침 하는 방법을 배워볼 예정이다.

첫번째 방법 -> URL를 타겟팅 하는 것이다
--> "NextJS야, /home 페이지와 연결되어있는 모든 데이터를 새로고침해줘" 라고 해주는 것이다. 이 방법이 가장 편한것이지만 단, 많은 제어권을 가질 순 없다고 한다.
Revalidate 버튼을 생성하고, 그것을 form 태그로 감싼 경우, 버튼을 클릭하면 form의 action 속성에 지정된 함수서버에서 실행된다.

--> 따라서 revalidate 함수에 'use server' 지시어를 추가해야 서버 컴포넌트에서 실행될 수 있다.

revalidate() 함수에서 revalidatePath() 를 사용했는데, 사용자가 Revalidate 버튼을 클릭하면, /home 경로에 대해 revalidatePath('/home') 가 호출되어 해당 경로의 데이터를 다시 유효화하게 된다. --> 그 결과,/home 페이지가 다시 생성되고 최신 데이터를 반영하게 된다.

태국 현지 국수로 title를 변경한 뒤, 화면에서 새로고침하면 아무 반응도 일어나지 않는다.
이때, Revalidate 버튼을 누르면 ? console에 hit이 출력된다. hit이 출력되는 것은? getInitialProducts() 함수가 호출됬다는 것을 의미한다.

그 후 페이지에서 새로고침을 하면 title이 태국 현지 국수로 변경된 것을 확인할 수 있다!


revalidateTag

revalidateTag를 사용하면, 태그를 가진 캐시를 무효화하고, 이후 데이터를 새로 가져올 수 있다.

이렇게 getProduct() 함수와 getProductTitle() 두개의 함수를 정의했다. 그리고 각각의 함수를 nextCache를 이용해서 각각의 함수를 첫번째 인자로 넘겨주고, 각각의 유니크한 key를 지정해 주었다.

여기서 처음 접하는 것은 tags이다.
tags캐시된 데이터를 관리하고 무효화하는 데 중요한 역할을 하는 메타데이터를 나타낸다.

여기서 getCachedProductTitle -> 'product-title'를 getCachedProduct -> product-detail tag를 다르게 각각 지정해 두었다.

tags의 이름은 key와 일치하지 않아도 된다. 하지만, key의 이름은 유니크 해야 한다.
여러 cache들은 똑같은 tags를 공유할 수 있다. -> 유일하지 않아도 되며, 여러 태그를 가질 수 있으며 또한 우리의 애플리케이션의 여러 cache에서 공유될 수도 있다.

자, 여기서 'product-title' 이라는 태그를 가진 cache만 새로고침을 시켜보도록 하겠다.
Revalidate 버튼을 생성한 뒤, form 태그로 감싼뒤, action에 revalidate 함수를 넘겨주었다.

revalidate 함수에서는 revalidateTag('product-title') 를 부르고 있는데 이는, revalidate 즉, 'product-title'을 가지고 있는 tag만 새로고침을 하겠다 를 의미한다고 이해하면 될 것 같다.

이렇게 title -> 태국 국soooooo로 변경했다.

getCachedProductTitle, getCachedProduct 둘다 title을 불러와야 하기 때문에 만약 둘다 새로고침이 이루어지면, 두개 다 같이 변경이 되어야 한다.

하지만 현재, 'product-title'만 새로고침이 이루어지고 있다. 위처럼 정말 'product-title'만 새로고침이 이루어져서 변경된 것을 확인할 수 있다.

두가지의 cache가 한 페이지에 존재하지만, 오로지 'product-title'만 새로고침이 되었고, 'product-detail'은 새로고침이 되지 않은 것을 확인할 수있다. --> tag로 인해 제어권이 생겼다.

경로를 기반으로 새로고침을 하면 그 경로의 모든 cache가 새로고침이 된다.
단, tag로 새로고침을 하면, 오직 이 태그를 가진 cache만 새로고침이 된다.

태그 하나가 여러 cache들을 새로고침할 수도 있음도 확인해보자.


getCachedProduct, getCachedProductTitle 모두에게 'xxx'라는 태그를 제공해 주었다.
그리고,revalidateTag('xxx'); 로 'xxx'태그를 가진 cache들을 새로고침 해줄게! 하고 명시해주고,

title -> 태국 국수 ⭐️ 로 변경하고, 버튼을 누르자, 두가지 모두 변경 된 것을 확인할 수 있다!

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글