![[Pasted image 20240616233539.png]]
export const metadata = {
title: 'Auth Layout',
description: 'Auth Layout',
};
export default function layout({children}: {children: React.ReactNode}) {
return (
<>
<h3>auth Component</h3>
{children}
<h3>auth footer</h3>
</>
);
}
// 폴더안의 layout을 거쳐서 루트 layout으로 이동
// metadata가 root에서만 쓸 수 있는게 아닌 다른 layout에서도 쓸 수 있다.
이렇게 하면 auth 경로에 layout이 적용이 되며 h3
태그가 적용이 된다. 루트 경로의 layout.tsx도 적용이 된다.
//metadata는 title, description 을 바꾸고 싶으면 수정하면 된다.
import Link from 'next/link';
export const metadata = {
title: {
// %s는 하위 layout에 metadata에서 title을 받아와서 적용이 된다.
// default는 루트에서만 쓰인다.
// /bolg는 세그먼트라 부르고 ?lang=ko는 쿼리스트링이라 부른다.
template: '%s | Hello, Next.js!',
default: 'Next.js APP | Nacoding',
},
description: 'First Next.js app!',
};
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
// 이래야 html body 태그가 생성된다.
<html lang="en">
<body>
<h1>Root Header</h1>
{children}
<Link href="/blog">로그인</Link>
<h1>Root Footer</h1>
</body>
</html>
);
}
export async function generateMetadata({params}: IPros) {
return {
title: `Blog ${params.id} | Sucoding`,
};
}
동적 라우팅에서 사용할 수 있는 함수로 비동기 형식으로 작동된다. 반환 값으로 Promise 객체를 반환하기에 params props로 받아와 동적 세그먼트를 사용하여 동적으로 설정가능하다.
세그먼트들은 각각 로딩 컴포넌트와 에러 컴포넌트를 가질 수 있다. 자신의 라우트 경로와 가장 인접한 loading.tsx
와 error.tsx
파일에서 보여진다.
import {AiOutlineLoading3Quarters} from 'react-icons/ai';
export default function loading() {
return (
<>
<h1>
<AiOutlineLoading3Quarters className="animate-spin" />
</h1>
</>
);
}
단 하위 컴포넌트들의 렌더링 처리가 병렬 처리 되기에 오래 걸리게 렌더링이 된 후 보여진다.
error.tsx
'use client';
export default function error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<>
{/* reset은 재시도 하게 할 수 있다. error 컴포넌트가 자체적으로 제공한다.
error 컴포넌트는 컴포넌트의 에러가 발생할 경우 처리되는 컴포넌트 경로마다 중첩이 가능하다.
가장 가까운 경로의 error 컴포넌트가 실행된다.
error 컴포넌트는 error와 reset을 props로 받아서 사용된다.
reset 함수를 실해하면, 이전 컴포넌트를 다시 불러온다.
단 이전 컴포넌트가 '클라이언트 컴포넌트'일 경우에만 가능하다.
*/}
Try Again!
</>
);
}
#### Suspense
suspense 컴포넌트로 감싼 각각의 비동기 컴포넌트에 대한 멀티 로딩 처리를 보여줄 수 있다. `fallback` 속성에 로딩 처리 중 보여줄 요소(예: 스켈레톤)를 전달한다.
```tsx
import ServerOne from '@/components/ServerOne';
import ServerTwo from '@/components/ServerTwo';
import {Suspense} from 'react';
export default function About() {
return (
<>
<h1>About Component</h1>
<Suspense
fallback={<div className="text-rose-500">suspense one Loading...</div>}
>
<ServerOne />
</Suspense>
<Suspense
fallback={<div className="text-blue-500">suspense two Loading...</div>}
>
<ServerTwo />
</Suspense>
</>
);
}
next.js는 성능 향상과 비용을 줄이기 위해 가능한 많이 캐시를 하는데 4가지의 매커니즘이 있다.
![[Pasted image 20240612095159.png | 600]]
매커니즘 | 대상 | 장소 | 목적 | 기간 |
---|---|---|---|---|
Request Memoization | fetch 함수의 return 값 | 서버 | React Component tree에서의 data 재사용 | request 생명주기 동안 |
Data Cache | Data | 서버 | 유저 요청이나 deplyoment 에 의해 저장된 데이터 | 영구적(revalidate 가능) |
Full Route Cache | HTML,RSC payload | 서버 | 렌더링 cost 감소 및 성능 형상 | 영구적(revalidate 가능) |
Router Cache | RSC Patload | 클라이언트 | 네비게이션에 의한 서버 요청 감소 | 세션 또는 정해진 기간 동안 |
Router Cacht는 클라이언트 사이드 캐시 또는 프리페치 캐시라고 부른다.
방문한 경로가 캐시되므로 즉시 뒤로/앞으로 이동하고, 프리페칭 및 부분 렌더링을 통해 새 경로로 빠르게 이동할 수 있다.
탐색 간에 전체 페이지를 다시 로드하지 않으며, React 상태와 브라우저 상태가 보존된다.
router cache를 지나갈 때 응답 받으면 뒤에 단계(Full router, request memoization, data cache)는 도달하지도 못함
Router Cache는 클라이언트에서 모든 페이지의 RSC Payload를 임시 캐싱하는 역할을 한다.
React Server Components - React18 에서 도입된 기능으로 서버와 클라이언트 간의 렌더링하는 방식을 변화 시킴 일부 컴포넌트를 서버에서 나머지를 클라이언트에서 렌더링해주는 페이로드 SSR과의 큰 차이점은 RSC payload 라는 JSON 포맷을 서버에서 직렬화하고 클라이언트에 전송한다.
시간이 지나면 자동으로 초기화가 된다. 쿠키를 초기화하거나 온디맨드 재검증(revalidateTag, revalidatePath) 을 해야함
자동으로 지속되는 시간은 최소 30초에서 최대 5분정도이다.
새로고침하면 초기화가 된다.
![[Pasted image 20240612114448.png|200]] ![[Pasted image 20240612114506.png|200]]![[Pasted image 20240612114635.png|200]]
즉 다른 경로를 갔다가 와도 router cache가 저장이 되었기에 변함이 없다. 새로고침을 하면 router cache 단계를 지나 request memoization , data cache 단계로 나아가는데 data cache 단계에서 처음 호출하는게 아닌 이미 존재하는 요청을 캐시로 저장을 하였다면 이미 캐시로 저장한 데이터를 반환한다.
Next.js 에서 자동적으로 빌드 시에 렌더와 캐시 작업을 하는데 이것은 서버에서 처리하는 것보다 로딩을 더욱 빠르게 해주는 매커니즘이다.
렌더링 작업은 경로 세그먼트와 서스펜스 단위에 따라 나눈다. 이때 경로 세그먼트를 캐시하는 것은 Router chach가 하는 작업으로 React Server Client Payload를 브라우저에 저장을 하지만 Full Router Cache는 서스펜스 단위로 작업을 하며 RSC Payload와 HTML을 지속적으로 저장하는 정적 렌더링 경로만 캐시한다.
즉 하이드레이션과 관련 있는 작업을 하는 cache이다.
Request Memoization의 작동 방식은 경로를 렌더링 하는 동안 처음 호출이면 메모리에 저장되지 않고 캐시가 됩니다 이때 이걸 MISS라 부르고 데이터 소스에 접근하였을때 HIT 상태가 됩니다. HIT 데이터는 함수를 실행하지 않고 메모리에 반환되며, [렌더링]이 완료 되면 메모리가 재성절 되며 모든 요청 메모이제이션이 지워진다.
Request Memoization은 React의 기능으로 GET 요청만 적용되는 기능이다.
![[Pasted image 20240612100835.png | 500]]
e.g) root layout과 a 페이지에서 두번 호출하고 있지만 실제로는 한 번만 호출되고 있다. 만약 b페이지에서도 요청을 하면 data cache를 확인하고 존재하면 캐시된 데이터를 리턴 받는다.
![[Pasted image 20240612094200.png | 300]]![[Pasted image 20240612114829.png|300]]
캐시는 React 구성 요소 트리가 렌더링을 완료할 때 까지 서버 요청의 수명 동안 지속 된다. 즉 영구적으로 존재한다.
즉 동일한 URL을 가진 api 요청을 자동으로 Request Memoization 하여 2번 요청할걸 1번으로 줄여주는 역할을 한다.
next.js에는 들어오는 서버 요청 및 배포 전반에 걸쳐 데이터를 가져온 결과를 유지하는 내장 데이터 캐시가 있다.
만약 {cache: 'no-store'}
의 경우 항상 데이터 소스에서 가져와서 메모제이션 된다. 즉 데이터가 캐시가 안되고 즉시 새로운 데이터가 적용이 된다. 그래서 최신의 데이터를 원할 때 사용한다.
우선적으로 router cache에 저장이 된다.
no-store
는 data chche를 초기화하는게 아닌 우회한다. 그래서 skip이라 한다.
router cache를 지나갈 때 응답 받으면 뒤에 단계(Full router, request memoization, data cache)는 도달하지도 못함
렌더링 중 fetch가 호출되면 데이터 캐시 응답을 확인하고 캐시가 존재할 경우 즉시 데이터가 반환되고 memoization 됩니다.
두 캐싱 메커니즘은 모두 성능 향상을 위해 도움이 되지만 데이터 캐시는 api 요청 및 배포 전반에 걸쳐 영구적으로 지속되지만 메모이제이션은 요청이 지속되는 기간동안만 지속된다.
![[Pasted image 20240611224738.png|500]]
일정 시간이 지나고 새로운 요청이 있을때 데이터의 유효성을 다시 검사하는 기능으로 자주 변경되지 않고 최신데이터가 그다지 중요하지 않을때 유용
fetch('https://...', { next: { revalidate: 3600 } })
처음으로 api를 요청하면 데이터 소스에 접근하여 데이터 캐시에 저장하고, 지정한 시간내에 예를 들면 5분이면 5분내에 호출되는 모든 요청은 캐시된 데이터를 반환한다. 그리고 시간이 지난 후에는 시간 기반 재검증을 트리거하여 새로운 데이터로 업데이트 한다.
이벤트를 기반으로 데이터를 재검증합니다. 태그 기반 검증과 경로 기반 접근 방식으로 나뉘는데 최신 데이터를 빨리 표시하려 할때 사용된다.
동일한 요청을 각기 다른페이지에서 사용할 때 revalidatePath
을 하게 되면 흐름상 다시 돌아오는게 없는데 내부 구조에 의해 값이 변경된다.
서버에서 데이터가 최신화된 다음 페이지에서 새로고침을 해도 변함이 없는데 revalidatePath
을 하게 되면 최신 데이터가 들어온다.
revalidatePath
가 지정한 경로만 최신데이터를 받아온다.
다른 경로는 새로고침시 전에 쓰던 데이터가 들어온다. 그치만 다른 경로를 갔다가 다시 그경로로 들어가면 data cache가 저장이 되어 데이터가 최신데이터로 캐시된다.
revalidateTag
는 {next: tags:['태그이름']}
는 태그이름을 담아서 보내기에 어떤 경로든 최신데이터를 받아올 수 있다. revalidatePath
보다 더 광범위한 전략이다.
즉 no-store -> 온디맨드 재검증 -> 시간 기반 재검증 순으로 데이터 최신화가 된다.
페이지 이동시 라우터 캐시
새로고침 데이터 캐시
cache: no-store
는 최신 데이터를 받아오는 속도는 빠르지만 전체적으로 캐시를 지우는 것이니 내가 원하는 페이지에서만 캐시를 지우고 싶을 때는 revalidate
를 사용하는 것이 더욱 효과적이다.
export const serverAction = (fomrData:FormData) => {
'use server'
const res = awiat fetch('~~' , {
method:'POST',
body: JSON.stringify(Object.fromEntries(formData)),
headers: {
'Content-Type': 'application/json'
}
})
if(res.ok){
revalidataePath('/')// 캐시 초기화
redirect('/')
}
}
우선 js에서 MongoDB를 사용하기 위해 mongoose라는 패키지를 설치해야한다.
npm install mongoose
import mongoose form'mongoose'
import mongose from 'mongoose'
const connectDB = async () => {
try {
if(mongoose.connection.readyState >=1) return
const conn = await mongoose.connect(process.env.MONGODB_URL as string)
} catch(err) {
console.log(err)
process.exit(1)
}
}
export default connectDB
import mongoose from 'mongoose'
const userSchema = new mongoose.Schema(
{
name: {
type:String
},
email: {
type:String
}
},
{timestaps: true} //시간 기록
)
export const User = mongoose.models.User || monoose.model('User', userSchema)
import {User} from './model';
import {connectDB} from '~~'
export async function getDB() {
connectDB()
const user = User.find({})
return user
}
import {User} from './model';
import {connectDB} from '~~'
export async function insertDB() {
connectDB()
const newUser = new User({naem,email}) //모델로 인스턴스 생성
newUser.save() //데이터 저장
}
import {User} from './model';
import {connectDB} from '~~'
export async function deleteDB(id:string) {
connectDB()
await User.findByIdAndDelete(id)
}
본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 1기 과정(B-log) 리뷰로 작성 되었습니다.