2025년 6월 3일
Notion api로 블로그 만들기 image 기간 제한 없이 사용하는 방법

안녕하세요. 오랜만에 글을 쓰네요. 오늘은 이 블로그를 만들면서 하나의 문제였던 image 기간 때문에 블로그 이미지가 사라지는 문제를 해결한 경험을 공유하겠습니다.
원본 코드는 이곳에 있습니다.
Notion Api 응답은 어떻게 올까?
우선 저는 @notionhq/client 이 라이브러리를 사용하여 만들었습니다. 이제 React의 useEffectEvent는 뭘까 ? 글을 기준으로 응답을 살펴보겠습니다.
notion.databases.query 응답
{
"tags": {
"id": "%3Ch%60t",
"type": "multi_select",
"multi_select": []
},
"id": {
"id": "DegF",
"type": "number",
"number": 4
},
"description": {
"id": "M%5CZ%5B",
"type": "rich_text",
"rich_text": [
{
"type": "text",
"text": {
"content": "useEffectEvent의 필요성, 사용 방법, 폴리필까지 한번에 알아보자",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "useEffectEvent의 필요성, 사용 방법, 폴리필까지 한번에 알아보자",
"href": null
}
]
},
"publish": {
"id": "M_%3E%60",
"type": "checkbox",
"checkbox": true
},
"thumbnail": {
"id": "pWE%60",
"type": "files",
"files": [
{
"name": "thumbnail-maker_202537.jpg",
"type": "file",
"file": {
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/0cde9f09-34e9-4761-9262-2c7efcbe4b59/993873e6-77db-4795-9890-686743b5c04d/thumbnail-maker_202537.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466Z775I5YQ%2F20250603%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250603T050127Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDUaCXVzLXdlc3QtMiJGMEQCIBlp2maakipYfpUM9GC%2BImqQsuXTr1lLnUAbdq9S6sqaAiA8BT8cgpIrpjiuSX9cT%2B%2B3MOGBqwl%2B0FOkiIidQbGAtiqIBAj%2B%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDYzNzQyMzE4MzgwNSIMC50wq%2F4J29FabnCwKtwDKr5pSHoVb01pkAHFpjMKVAKm2wK4qFsUNGRXmOWAelkeqW8mmoFopZyKBe1iYZzQ8tTDyWA%2Fh6VPBfOnS5t9y05BYR1DhjTV3KmhylVNnFZwVa14A5lgGzPuwtHj0O3SYA4fmJPwPmLr06BzjnCzzel9GTY3bzf%2BMPDX9Wehmfs6Z1YzF%2B3FFR7NyJj4scAr6mTG3xoQEdjZ6HMKvTFsNCmBK40KAZ3PnUeHgC8HQN%2FyCf1Ukpwoq4wPhAvOo7DKJOJwabfNH4wsjmCmaY%2FBo4KfWJd6mg%2BxbdzSbAhtXGR%2BGCVHN6QqE0ECcoaP6Bwqr%2B0MZ2oBFSoms%2FTP0jCSFTF4xBl%2BK2ua22tDrczfzAx4rzHvNDWw0XOn0OUIn6iWjkkdw4z8RhALzIGNhRMG7qNmJKp73qkI6WcwhOF1kPmiyOdoToJ%2FvWB8R0WwR81zXZfEiQukOZ4CK%2FKd7w5CytPzSo4psjXHVigk74nx4xiOsE6UbjkBFbPQtTavOMGiKtsXrCFLzOgPDJhx1su4Up1VtFHPegyJRZ8rEF1BgfCHPsIRsF0QDp4QEsOeGUdL9j0tNSxlic3ls2toWVk7u2EP8obJUwU0%2BiAZWIJWDTHeln0ReuHj4IAmf0owroH6wQY6pgEi%2FVhezj6VtAKYc1my3nTkx7rL08RWwTRwW73hl5kSCzZzU0ofzWx4G%2FwZcnL9%2FofUxDJxiJpwy7mSDlagYLwsMNx62bkMYF9YqBXxynHS0tVLStnMDXnuIdgObMXlV0ozgXhQYaVnFW5IpxRf6or3HcWcREBMsj%2FxoRL4RfEMy8tbWy3c7oHtzB%2FkQTyhYy4LSGFStQilsGMPjg3rU3mbQFGuTz1X&X-Amz-Signature=16a090a38992071538513e0c354d58d53cce51a93af9aaf89222c6c22acd07a2&X-Amz-SignedHeaders=host&x-id=GetObject",
"expiry_time": "2025-06-03T06:01:27.952Z"
}
}
]
},
"publishDate": {
"id": "sZx%3B",
"type": "date",
"date": {
"start": "2025-03-07",
"end": null,
"time_zone": null
}
},
"title": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "React의 useEffectEvent는 뭘까 ?",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "React의 useEffectEvent는 뭘까 ?",
"href": null
}
]
}
}응답이 오는 것을 확인할 수 있습니다.
"thumbnail": {
"id": "pWE%60",
"type": "files",
"files": [
{
"name": "thumbnail-maker_202537.jpg",
"type": "file",
"file": {
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/0cde9f09-34e9-4761-9262-2c7efcbe4b59/993873e6-77db-4795-9890-686743b5c04d/thumbnail-maker_202537.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466Z775I5YQ%2F20250603%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250603T050127Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDUaCXVzLXdlc3QtMiJGMEQCIBlp2maakipYfpUM9GC%2BImqQsuXTr1lLnUAbdq9S6sqaAiA8BT8cgpIrpjiuSX9cT%2B%2B3MOGBqwl%2B0FOkiIidQbGAtiqIBAj%2B%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDYzNzQyMzE4MzgwNSIMC50wq%2F4J29FabnCwKtwDKr5pSHoVb01pkAHFpjMKVAKm2wK4qFsUNGRXmOWAelkeqW8mmoFopZyKBe1iYZzQ8tTDyWA%2Fh6VPBfOnS5t9y05BYR1DhjTV3KmhylVNnFZwVa14A5lgGzPuwtHj0O3SYA4fmJPwPmLr06BzjnCzzel9GTY3bzf%2BMPDX9Wehmfs6Z1YzF%2B3FFR7NyJj4scAr6mTG3xoQEdjZ6HMKvTFsNCmBK40KAZ3PnUeHgC8HQN%2FyCf1Ukpwoq4wPhAvOo7DKJOJwabfNH4wsjmCmaY%2FBo4KfWJd6mg%2BxbdzSbAhtXGR%2BGCVHN6QqE0ECcoaP6Bwqr%2B0MZ2oBFSoms%2FTP0jCSFTF4xBl%2BK2ua22tDrczfzAx4rzHvNDWw0XOn0OUIn6iWjkkdw4z8RhALzIGNhRMG7qNmJKp73qkI6WcwhOF1kPmiyOdoToJ%2FvWB8R0WwR81zXZfEiQukOZ4CK%2FKd7w5CytPzSo4psjXHVigk74nx4xiOsE6UbjkBFbPQtTavOMGiKtsXrCFLzOgPDJhx1su4Up1VtFHPegyJRZ8rEF1BgfCHPsIRsF0QDp4QEsOeGUdL9j0tNSxlic3ls2toWVk7u2EP8obJUwU0%2BiAZWIJWDTHeln0ReuHj4IAmf0owroH6wQY6pgEi%2FVhezj6VtAKYc1my3nTkx7rL08RWwTRwW73hl5kSCzZzU0ofzWx4G%2FwZcnL9%2FofUxDJxiJpwy7mSDlagYLwsMNx62bkMYF9YqBXxynHS0tVLStnMDXnuIdgObMXlV0ozgXhQYaVnFW5IpxRf6or3HcWcREBMsj%2FxoRL4RfEMy8tbWy3c7oHtzB%2FkQTyhYy4LSGFStQilsGMPjg3rU3mbQFGuTz1X&X-Amz-Signature=16a090a38992071538513e0c354d58d53cce51a93af9aaf89222c6c22acd07a2&X-Amz-SignedHeaders=host&x-id=GetObject",
"expiry_time": "2025-06-03T06:01:27.952Z"
}
}
]
},이 응답에 주목해 보겠습니다.
expiry_time값이 있는데요. notion은 자체 s3에 expiry_time 시간 동안만 asset을 저장합니다. 저는 블로그를 SSG로 빌드를 해서 배포하기 때문에 저 expiry_time가 만료되면 더 이상 이미지를 불러올 수 없겠죠.
notion-to-md 응답
이번에는 notion-to-md의 pageToMarkdown 응답 값을 살펴보겠습니다.
{
"type": "image",
"blockId": "1afa463e-8303-8081-87f1-f5d413c0c3b7",
"parent": "",
"children": []
}이런 식으로 응답이 오는 것을 확인할 수 있습니다.
publish 기능을 활용하여 image 가져오기
Notion 페이지를 publish를 하면 이런 식으로 https://nimble-monkey-1a1.notion.site/React-useEffectEvent-1afa463e830380b59415c0a2b503a729 url이 나옵니다.
이제 우리는 https://nimble-monkey-1a1.notion.site base url을 얻었습니다. 이제 image src로 들어온 https://prod-files-secure.s3.us-west-2.amazonaws.com/0cde9f09-34e9-4761-9262-2c7efcbe4b59/bb8a9cc…. 값을 수정하여 publish page에서 이미지를 가져오는 함수를 가져오겠습니다.
const NOTION_PUBLIC_URL = "https://nimble-monkey-1a1.notion.site";
interface CreateNotionImageUrlParams {
fileUrl: string;
id: string;
width?: number;
}
export function createNotionImageUrl({
fileUrl,
id,
width,
}: CreateNotionImageUrlParams): string {
const encodedUrl = encodeURIComponent(fileUrl.split("?")[0]);
return `${NOTION_PUBLIC_URL}/image/${encodedUrl}?table=block&id=${id}&cache=v2&width=${width}`;
}이제 Image를 사용하는 부분에서
return (
<Image
{...rest}
className={css({
display: "block",
margin: "0 auto",
marginBottom: SPACE.sm,
})}
alt={alt || ""}
src={createNotionImageUrl({
fileUrl: src || "",
width: IMAGE_SIZE,
id: blockId,
})}
width={IMAGE_SIZE}
height={IMAGE_SIZE}
/>
);
}이런 식으로 사용할 수 있습니다.
