포트폴리오를 만드는 과정에서 노션 API를 사용해 데이터베이스에 있는 이미지를 받아와서 띄워주는데, 일정 시간 후에 이미지가 보이지 않는 문제가 발생했다.
처음에는 Next.js의 자동 캐싱 문제인가,, 하고 캐싱을 중지하는 옵션 등을 시도해봤는데 모두 실패했다. 한참을 고민하다 임시로 프로젝트에 이미지를 저장하여 띄워주는 식으로 처리해놨었다.
{projects.map((project) => {
const coverImg = `/cover/${project.properties.id.number}.png`;
return (
<div>
<img
src={coverImg}
alt="project image"
/>
// ... 기타
</div>
);
})}
노션에서 데이터를 가져와 뿌려주는 애플리케이션인데.. 이 방식은 너무 비효율적이라고 느껴져 해결책을 찾기 위해 열심히 고민했다. 🤯
문제의 원인은 이미지 URL의 유효기간이 설정되어 있기 때문이다. 노션 API에서 제공하는 이미지 URL에는 유효기간이 포함되어 있어 일정 시간이 지나면 만료된다.
return (
{projects.map((project) => {
const coverUrl = project.cover?.external?.url || project.cover?.file?.url;
console.log(coverUrl);
return (
<div>
<img
src={coverUrl}
alt="project image"
/>
// ... 기타
</div>
);
})}
)
처음과 같이 임시로 프로젝트에 저장된 파일을 가져오는 방식이 아닌 노션에서 받아온 데이터의 이미지 URL을 콘솔에 찍어보면 아래와 같이 출력된다.
https://prod-files-secure.s3.us-west-2.amazonaws.com/42df9ae9-743b-4d89-a5c9-681fdfaad2ce/a463f47e-eb8e-4c05-9eaa-f6aa0a10d930/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2024-05-10_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_6.24.35.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240603%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240603T095659Z&X-Amz-Expires=3600&X-Amz-Signature=002bf01afe27d5d8bbe15ff5141b18213920e18e0f0cea7e5133e38d18ea8cce&X-Amz-SignedHeaders=host&x-id=GetObject
해당 url을 살펴보면 Amz-Expires=3600
이라는 옵션이 설정되어있다. 이는 3600초, 즉 한 시간 동안만 유효하다는 것을 의미한다.
해결법은 다양했다. AWS S3를 설정하여 이미지를 직접 관리할 수도 있고, 주기적으로 이미지를 갱신할 수도 있고 ... 여러가지가 있지만 복잡하지 않으면서 획기적인(?) 방식으로 해결한 과정을 찾게 되어 참고하여 진행했고, 도움이 많이 되었다..! (참고한 글은 아래에 출처로 남겨놓았다.)
사용한 방법은 노션 페이지를 웹에 공유하여 생성되는 링크를 활용하는 것이었다. 웹에 게시된 이미지를 가져오게 되면 경로가 다르기 때문에 URL이 만료되지 않고 이미지를 안전하게 가져올 수 있다.
먼저, 노션 데이터베이스 페이지를 웹에 공유하고 해당 이미지 URL을 가져와보았다.
https://candy-icebreaker-bbb.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F42df9ae9-743b-4d89-a5c9-681fdfaad2ce%2Fa463f47e-eb8e-4c05-9eaa-f6aa0a10d930%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-05-10_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_6.24.35.png?table=block&id=e2c7cc5a-73b7-489f-b9f6-e3ffa1dc67f3&spaceId=42df9ae9-743b-4d89-a5c9-681fdfaad2ce&width=2000&userId=&cache=v2
이 URL을 살펴보면 문제가 되었던 Amz-Expires=3600
옵션이 포함되어 있지 않은 것을 확인할 수 있다.
이제 이 링크를 가져와 뿌려주면 해결..인데 이 부분도 꽤나 많은 고민이 필요했다.
먼저, 노션에서 받아오는 원본 이미지 URL를 가공하여 웹에 게시된 이미지 URL을 만들어야 했다.
이를 위해 아래와 같이 최종적인 이미지 URL을 생성하는 함수를 만들었다.
const getPublishedImageUrl = (notionCoverUrl, projectId) => {
const encodedUrl = encodeURIComponent(notionCoverUrl.split("?")[0]);
return `https://candy-icebreaker-bbb.notion.site/image/${encodedUrl}?table=block&id=${projectId}&cache=v2`;
};
원본 URL의 쿼리 파라미터 대신 웹에 게시된 URL의 쿼리 파라미터를 사용해야 하기 때문에 split
메소드를 사용하여 URL을 추출했다. 공백이나 특수 문자에 대응하기 위해 encodeURIComponent
로 인코딩을 해주었고, 원본 URL에서 필요한 부분을 파라미터로 처리했다. cache
옵션은 성능 최적화를 위해 노션에서 제공하는 브라우저 캐시 설정으로, 기본 값 그대로 사용했다.
그 후 fetchProjects
함수에서 데이터를 가져올 때, 이미지를 가공하여 변환된 URL을 사용하도록 수정했다.
const fetchProjects = async () => {
const notion = new Client({ auth: process.env.NOTION_TOKEN });
const response = await notion.databases.query({
...// 기타 로직
});
return response.results.map((project) => {
const notionCoverUrl =
project.cover?.external?.url || project.cover?.file?.url;
const coverUrl = getPublishedImageUrl(notionCoverUrl, project.id);
return {
...project,
coverUrl,
};
});
};
마지막으로, 변환된 이미지 URL을 사용하여 이미지를 렌더링 하도록 처리하면 완성~!
return (
{projects.map((project) => {
// 기존에 정의했던 coverUrl 제거
return (
<div>
<img
src={project.coverUrl} // 위에서 추가한 데이터 그대로 사용
alt="project image"
/>
// ... 기타
</div>
);
})}
)
아래와 같이 시간이 지나도 정상적으로 출력되는 것을 확인할 수 있었다..!
이 과정으로 외부 API를 사용할 때 더욱 주의 깊게 확인해야겠다는 생각이 들었다. 단순히 이미지 경로에서 동일한 문제가 생기는 것을 인지했을 때 바로 그 경로를 주의 깊게 살펴봤으면 Expires
옵션을 발견하여 시간이 많이 단축되었을텐데 .. 🥺 단순하게, 직관적으로 생각하지 못해 많이 돌아간 느낌이다.
그래도 이러한 과정을 공유해주신 개발자님 덕분에 생각지도 못했던 방식으로 접근해볼 수 있어서 너무나 재미있게 해결해 나갈 수 있었다. 나도 꾸준히 열심히 해서 누군가에게 이러한 도움이 될 수 있으면 참 좋겠다는 생각을 했다. 영차영차 ~~ 🐢
📌 참고한 글 : DEZANG.net님 블로그