Seung Hun

2025년 6월 3일

Notion api로 블로그 만들기 image 기간 제한 없이 사용하는 방법

안녕하세요. 오랜만에 글을 쓰네요. 오늘은 이 블로그를 만들면서 하나의 문제였던 image 기간 때문에 블로그 이미지가 사라지는 문제를 해결한 경험을 공유하겠습니다.

원본 코드는 이곳에 있습니다.

Notion Api 응답은 어떻게 올까?

우선 저는 @notionhq/client 이 라이브러리를 사용하여 만들었습니다. 이제 React의 useEffectEvent는 뭘까 ? 글을 기준으로 응답을 살펴보겠습니다.

notion.databases.query 응답

javascript
{
    "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
            }
        ]
    }
}

응답이 오는 것을 확인할 수 있습니다.

javascript
"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-mdpageToMarkdown 응답 값을 살펴보겠습니다.

javascript
{
    "type": "image",
    "blockId": "1afa463e-8303-8081-87f1-f5d413c0c3b7",
    "parent": "![1afa463e-8303-8081-87f1-f5d413c0c3b7:thumbnail-maker_202537.jpg](https://prod-files-secure.s3.us-west-2.amazonaws.com/0cde9f09-34e9-4761-9262-2c7efcbe4b59/3fb8b350-da78-45d8-8eba-65206fc344e2/thumbnail-maker_202537.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663XRA3FW4%2F20250603%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250603T052040Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDUaCXVzLXdlc3QtMiJHMEUCIQCI3GmP7S3MeOyO0I4CWG3NILw%2BSeRWo5n2RJpT3F26qwIgQISiWw%2F%2BIQcCKCDEOD2%2Bf9qq%2F2uGnbEWsKBFQi%2FuqnkqiAQI%2Fv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDI3N7GeZYfOTVH6r5yrcAwcnYzY6mmPpVHralqJFCTWdomLxle6Dgne0%2F3xM4lGa0pOd%2Fe%2BRTph54iCIBJDFXetO775Q%2F4zOWK0cseKsJSbgYVHRdgxErxkCIpIb1KobQbLSyJBcTa8FeZB%2FgUJza8W5l%2BFOnL7byZHKAO%2Fqe2DzBLjeWQ2eNPzUDXC9XtcwRj%2B%2BYjG62lBDT0MsxIrk%2FUrDZKuv9D5O5%2BH4S8edPfveEA%2FVRuiYHOyqKyM0fey94ANuqRqigXSLi4DF6xVRMgYPuhevhPR9zXYP4eXFLKhOOGkpeL2mm28j9pweI3OhMUFxnalzMjv5pA%2By%2BiX0BG4hhRa9M6nfIeIF3HN9bfLrjlX%2FmamBf8%2BxARSoKYCoSBA4pgSelHSKwgAEFyqmmaJzN1DGRYFsPrrXPwjACoX6Vq8d41rsxKLHt6wCRe4oXE2bpzfLPZkS9hfB4Z00zZKLfPqg7FrTVTXxObR1vucCs5slSu3ueqTcDnzyWDK%2BRooCrn9JjKWfVN1lsqTDgHxxnm4wbaD8%2Bo328lDS1jwLI7SnAVRT%2FBEF8s8QEl37XoQM6BJAnB5Surjc%2FtTyM2N4fpD%2BLDuwqrlIAQRqcqELvGLqLQL8NG4UDml1NKVRUvt9nHkTwZRZaJ0%2FMLGB%2BsEGOqUBiWNXJJeOAg5qy5HIJmg5OjhWiD6wKSYH7ggi4NpinWDaRSf6koX8P12qUiOaKQFwAftghwai5A9hnG9zu3bIHlewe8wpEBhjEh00TwfNaAcBTC37Y9YMYkR47%2FB3eYaQW3un9GENfQTIpgU%2F8hoXugYTLQmu1K70Poenq7vbkClBAOOI9LVHy0k3T%2BW%2Be43a28jUeF6U31Uv13I7b%2BolRyRR98EJ&X-Amz-Signature=c3d830518020693647a55495eec2d0cf9955dd259b519ec148ab41470741f030&X-Amz-SignedHeaders=host&x-id=GetObject)",
    "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에서 이미지를 가져오는 함수를 가져오겠습니다.

javascript
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를 사용하는 부분에서

javascript
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}
            />
          );
        }

이런 식으로 사용할 수 있습니다.