metadata
// root layout
export const metadata = {
title: "Hello Word"
description: "Let's learn next.js",
};
// root layout
export const metadata = {
title: {
template: '%s | Hello Word',
default: 'Next.JS 14 | Hello Word',
},
description: "Let's learn next.js",
};
------------------------------------------------
// login 폴더 -> layout
export const metadata = {
title: 'Login',
};
위 코드 처럼 template에 %s를 설정하고 login폴더의 layout의 metadata를 보면 title의 %s에 "Login"이 대체가 되어 Login | Hello Word의 title이 보여지게된다.
// blog 폴더 -> [id] 폴더 -> layout
interface IMetaProps {
params: {
id: string;
};
}
export const generateMetadata = async ({ params }: IMetaProps) => {
return {
title: `Blog ${params.id} | Hello World`,
};
};
만약 blog/2... blog/10 이런식의 동적 경로였을때 위처럼 generateMetadata 함수와 params를 활용해서 페이지별로 동적 메타데이터를 설정할 수 있다.
병렬 로딩
// ServerOne 컴포넌트
export default async function ServerOne() {
await new Promise((resolve) => setTimeout(resolve, 4000)); // 4초 딜레이
return (
<>
<h1>ServerOne Component</h1>
</>
);
}
------------------------------------------------------------
// ServerTwo 컴포넌트
export default async function ServerTwo() {
await new Promise((resolve) => setTimeout(resolve, 2000)); // 2초 딜레이
return (
<>
<h1>ServerTwo Component</h1>
</>
);
위의 각각 컴포넌트를 부모컴포넌트에 불러와 실행하게되면 ServerOne 컴포넌트는 4초 ServerTwo 컴포넌트는 2초의 딜레이로 총 로딩시간은 6초가 아니라 병렬 로딩으로 가장 로딩이 늦은 컴포넌트 시간인 4초가 총 로딩시간이된다.
❗️ 그러면 4초라는 로딩 지연 시간동안 사용자는 무슨 일이 일어나고 있는지 알 수 없고 사용자 경험에도 좋지 않다.
그래서 컴포넌트의 로딩 상태관리를 해줘야한다.
// about 폴더 -> page 파일
export default function About() {
return (
<>
<h1>About Component</h1>
<ServerOne />
<ServerTwo />
</>
);
}
// about 폴더 -> loading 파일
export default function loading() {
return (
<>
<AiOutlineLoading3Quarters className="w-10 h-10 animate-spin" />
<h1>About loading ...</h1>
</>
);
}
위처럼 loading 시스템 파일을 활용해서 4초의 로딩지연 시간동안 로딩 컴포넌트를 보여줌으로써 사용자에게 로딩 중이라는 상황을 전달하여 사용자 경험에 좋은 영향을 줄 수 있다.
// about 폴더 -> page 파일
export default function About() {
return (
<>
<h1>About Component</h1>
// 해당 컴포넌트를 Suspense로 감싸줘야한다.
// 로딩 될때 보여지는 부분을 fallback에 정의
<Suspense fallback={<h1 className="text-blue-500">ServerOne 로딩중...</h1>}>
<ServerOne />
</Suspense>
<Suspense fallback={<h1 className="text-orange-500">ServerTwo 로딩중...</h1>}>
<ServerTwo />
</Suspense>
</>
);
}
위 처럼 병렬로 처리되면서 각각 컴포넌트가 개별로 로딩이 가능하며 먼저 로딩이 되는 컴포넌트 순으로 랜더링 된다.
error
// error 컴포넌트
'use client';
export default function BlogError({ error, reset }: { error: Error; reset: () => void }) {
return (
<>
<h1>BlogError Component : {error.message}</h1>
<button onClick={reset}>try again</button>
</>
);
}
=> error 컴포넌트는 경로마다 중첩하여 지정할 수 있다.
// blog 컴포넌트
'use client';
import BlogCard from '../components/blogCard';
export default function Blog() {
return (
<>
<h1>Blog Component</h1>
<BlogCard />
</>
);
}
--------------------------------------
// blogCard 컴포넌트
export default function blogCard() {
let random = Math.floor(Math.random() * 10 + 1);
console.log(random);
if (random < 3) {
throw new Error('Random Test Error');
}
return (
<>
<h1>blogCard Component</h1>
</>
);
}
error test 조건에 따라 랜덤하게 BlogCard 컴포넌트가 에러를 던지면 가장 가까운 경로에 있는 error 컴포넌트를 찾아서 에러 페이지가 렌더링되고 다시 시도하는 버튼으로 reset 함수를 호출시켜 다시 시도를 수행한다.