외부 렌더러 라이브러리를 사용해 구현한 프로젝트 상세 페이지 로드 시 긴 시간의 로딩이 발생

Next.js의 Cache관련 공식 문서를 살펴보면 fetch('https://...', { cache: 'force-cache' })를 통해 캐시를 적용시키거나 fetch('https://...', { next: { revalidate: 3600 } })를 통해 캐싱 시간을 설정할 수 있습니다.
import { PROJECT } from "@/type/project";
export async function fetchNotionDb(): Promise<PROJECT[]> {
try {
const notionApiUrl = `https://api.notion.com/v1/databases/${process.env.NOTION_DATABASE_ID}/query`;
const response = await fetch(notionApiUrl, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28",
"Content-Type": "application/json",
},
body: JSON.stringify({
sorts: [
{
property: "ID",
direction: "ascending",
},
],
}),
// fetch 요청에 cache 옵션을 추가
next: { revalidate: 3600 }
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching Notion data:", error);
throw error;
}
}
fetchNotionDb()메서드는 Home과 projectDetail에서 두번 호출되는데 이를 캐싱하여 중복 호출 문제를 해결하고 응답 속도를 개선하였습니다.

데이터를 캐싱하여 응답속도를 조금 개선했지만 여전히 불만족스러운 속도입니다. 프로젝트 상세 페이지는 한번 작성하면 거의 변화가 없기 때문에 SSG로 변경합니다.
최신화를 위해 매번 빌드를 할 수 없기 때문에 ISR을 통해 설정된 시간 마다 최신화해 응답속도를 개선합니다.
기존엔 getStaticProps같은 메서드를 활용했겠지만 현재 사용중인 Next.js 15 버전에선 다른 방식을 사용해야 합니다. 관련 공식 문서 링크를 살펴보면 동적 라우팅의 으로 페이지를 빌드 시점에 미리 생성하려면 generateStaticParams메서드를 사용합니다. 그리고 재검증 주기를 설정하여 해당 페이지에 대한 요청이 들어오면 재검증되어 최신화됩니다.
export const revalidate = 3600;
export async function generateStaticParams() {
const projects: PROJECT[] = await fetchNotionDb();
return projects.map((project) => ({
slug: project.slug,
}));
}
동적 페이지를 정적 페이지로 빌드 시점에 생성하고, 최신화를 위해 ISR을 결합하여 응답속도를 개선했습니다.

커버 이미지 URL을 보면 X-Amz-Expires=3600이라는 파라미터가 있습니다. 이 값은 이미지 URL이 3600초, 즉 1시간 뒤에 만료된다는 의미입니다. 만약 사용자가 페이지를 새로 고침하거나 오래된 이미지 링크를 사용하면, 이미지가 만료되어 더 이상 접근할 수 없게 됩니다.
"url":"<https://prod-files-secure.s3.us-west-2.amazonaws.com/이미지 주소.png
?X-Amz-Algorithm=blahblah
&X-Amz-Content-Sha256=blahblah
&X-Amz-Credential=blahblah
&X-Amz-Date=blahblah
&X-Amz-Expires=3600 // 여기 만료 시간!!!
&X-Amz-Signature=blahblah
&X-Amz-SignedHeaders=host
&x-id=GetObject>"
1. 만료시간 문제를 해결하기 위해 해당 페이지를 공유합니다.

2. 공유된 페이지에서 커버 이미지 URL을 추출합니다.
이 URL은 만료 시간이 없기 때문에, 더 이상 만료 시간 문제를 걱정할 필요가 없습니다.
https://superficial-amber-09e.notion.site/image/인코딩된 이미지 주소.png
?table=block
&id=DB에서 해당 PageId
&spaceId=blahblah
&width=2000
&userId=
&cache=v2
3. 커버 이미지 URL 자동화
해당 커버 이미지 URL을 직접 하드코딩 하는 방법도 있겠지만 아래 함수를 통해 커버 이미지로부터 만료시간이 없는 URL을 추출합니다.
const transformCoverUrl = (cover: any, pageId: string): string => {
if (cover?.external?.url) {
return cover.external.url;
} else if (cover?.file?.url) {
const encodedUrl = encodeURIComponent(cover.file.url);
// 새로운 URL 생성
const newCoverUrl = `https://superficial-amber-09e.notion.site/image/${encodedUrl}?table=block&id=${pageId}&spaceId=&width=2000&userId=&cache=v2`;
return newCoverUrl;
}
return "";
};
이 방법을 통해 Notion에서 제공하는 커버 이미지 URL을 안전하게 사용할 수 있고, 만료 시간에 대한 걱정을 하지 않아도 됩니다.