개요
저번 블로그에서 MDX 라이브러리를 사용하고 있었지만, 코드펜 임베딩, 그리고 rich-text 내부의 하이라이트된 부분 처리하기 등등 너무다 다양한 상황에 대한 스타일링을 처리하기 어려웠어요. MarkDown으로 표현된 내용을 노션에서 작성된 것 같이 스타일링 하기 위해서는 너무 많은 부분을 직접 구현해야된다는 것을 깨달았어요.
하지만 직접 구현하기도 힘든 점이 그런 다양한 경우의 수를 MDX로 구분할 수 없다는 점이였어요. 여러가지 한계로 인해서 react-notion-x 라이브러리로 갈아타게 되었어요.
react-notion-x 라이브러리
react-notion-x라이브러리는 주간 다운로드가 2만7천회가 넘는 아주 핫한 라이브러리에요. 그리고 최근 업데이트는
2025-08-30일 기준 한 달 전이였어요. 그만큼 인기있는 라이브러리이지만, 구현하면서 깨달은 점은 역시 모든 라이브러리가 완벽할 수 없다는 것을 깨달았어요.React-Notion-X 라이브러리의 이슈 사항
저는 이 라이브러리를 사용하면서 한 가지 드러나는 문제점과 내재된 문제점이 존재한다는 것을 알게 되었어요. 물론 문제라는 것이 제가 노션을 사용하는 방식에서 문제가 되는 부분인 것이지, 다른 분 들께는 문제가 아닐 수도 있을것 같아요.
이번 블로그에서는 드러나는 문제점에 대해서 다루어보고, 내재된 문제점에 대해서는 다음에 다루어 보도록 할게요
react-notion-x가 이미지를 다루는 방식
아무래도 직접 사진들을 저장하고 사용하는 방식이 아니라서 노션이 어떻게 이미지를 다루는지, 또
react-notino-x는 사진을 어떻게 다루는 지 알아야 했어요. 노션이 이미지를 어떻게 다루는 지는 지난 블로그에 집중적으로 다루었으니 참고 부탁드려요.
이해를 돕기 위해서 아주 간단하게 블로그의 내용을 말씀드릴게요. 노션에서 업로드 된 이미지의 원본 URL을 확인해 보면
www.notion.so/image/attchment? 방식의 URL을 사용해요. 그리고 해당 URL을 주소에 입력하면 바로 s3 signedURL로 리다이렉트되어서 사진을 반환해줘요.노션에서 사용되는 이미지 주소가 s3 signedURL로 리다이렉트됨
이 것은 약간의 버그같은 것이기도 한데, 기존 노션에서 api로 받게 되는 사진 주소는 일반적으로 signedURL로 제공받게 되어있어요. 하지만
www.notion.so/image/attachment 를 사용하게 되면 만료시간이 없는 링크가 되어버린다는 사실을 알게되었어요. (만료시간이 없다는 뜻은 마음껏 nextjs의 캐싱전략을 사용해도 된다는 뜻이기도 하죠)하지만 해당 접두어의 이미지들은 만료시간없는 링크로 사용할 수 있었지만,
gif 는 전체 재생이 안된다는 문제점이 있었다는 것을 저번 블로그에서 다루었었지요.한 마디로 말하자면,
gif 파일은 s3 signedUrl을 원본 그대로 사용해야한다는 점이 핵심이에요.react-notion-x 라이브러리가 이미지를 사용하는 방식
react-notion-x 라이브러리 제작자 역시 노션의
www.notion.so/image/attachment 접두어가 붙은 링크의 버그를 알고 있었던것 같아요. 라이브러리를 확인했더니, 똑같은 코멘트가 있었어요. 그렇기 때문에 해당 패키지는 모든 이미지 URL을
www.notion.so/image/attachment 접두어를 붙여 변환하는 로직을 사용하고 있었어요. 아래 사진은 node_modules/react-notion-x 폴더에서 이미지 url 매핑 로직에 console.log로 url이 어떻게 변환이 되는지 확인해보았어요. node_modules/reac-notion-x/build/index.js
원본 이미지 url에서 변환된 이미지 url 콘솔 로그
JavaScript
// 원본 이미지 주소
👉👉👉👉👉👉source https://file.notion.so/f/f/b4216657-966f-4c29-ae8c-42f6c4adb66d/5de621d9-ab90-4ede-aae4-526de18d77ec/image.png?table=block&id=25f9c76c-6cb4-80b5-9e75-cb062450ed3c&spaceId=b4216657-966f-4c29-ae8c-42f6c4adb66d&expirationTimestamp=1756742400000&signature=g57sq_Icg1-9EaPi95LMru0KAYAityFG2il_HlU826k
// 변환된 이미지 주소 - 만료시간이 없음
👉👉👉👉👉👉src https://www.notion.so/image/attachment%3A5de621d9-ab90-4ede-aae4-526de18d77ec%3Aimage.png?table=block&id=25f9c76c-6cb4-80b5-9e75-cb062450ed3c&cache=v2콘솔로그를 확인해보면 어떻게 이미지 url이 변환이 되는지 확인이 되시죠?
만약 노션 사용자가 gif를 사용할 일이 거의 없다면 이 부분을 굳이 수정하지 않아도 될 것 같지만, 저의 경우에는 블로그에
gif를 아주 많이 사용하기 때문에 꼭 수정을 했어야 했어요. 이 부분을 해결하기 위한 여러가지 방법을 적용해보게 되었어요. ( 사실 이것 저것 해보다가 제가 만족스럽지 않아서 계속 바꿔보다가 이르게된 결론이에요 )Patch-package를 사용해서 gif로직 수정
patch-package라이브러리는 배포된 패키지의 수정사항이 필요한 경우 버전을 유지하고 커스터마이징을 용이하게해주는 라이브러리에요. 이거에 대한 내용은 따로 설명하기로 하고, 이것으로 라이브러리를 어떻게 수정했는 지 한 번 알아보도록 할게요. patch-package 라이브러리 사용법은 총 세 가지 단계를 따르면되요.
- 의존성 설치
JavaScript
npm i patch-package- package.json에 script 추가
JavaScript
"scripts": {
"postinstall": "patch-package"
}이 스크립트가 추가가 되어야,
npm i 로 패키지를 설치하고 후처리 작업으로 패칭작업을 하게 되어요.- 패칭 실행
JavaScript
npx patch-package <패키지이름>작업이 완료되면 해당명령어로 패칭을 하면 되요!
패키지 수정 작업
그럼 본격적으로 react-notion-x 패키지를 수정해보도록 할게요.
gif파일이 아닐 때만 url 매핑 사용하도록 수정
JavaScript
// node_modules/react-notion-x/index.js 787번째 라인
else if (block.type === "image") {
if (!source.includes(".gif")) {
if (source.includes("file.notion.so")) {
source = (_k = (_j = (_i = block.properties) == null ? void 0 : _i.source) == null ? void 0 : _j[0]) == null ? void 0 : _k[0];
}
}
const src = mapImageUrl(source, block);따라서 위와같이 ‘.gif’가 아닐 때만 로직이 그대로 적용이 되게 수정했어요. 하지만 여기서 끝나는 문제는 아니였어요.
react-notion-x라이브러리는 유기적으로 연결되어 있는 형제 패키지들이 존재하는데, 그 중 notion-utils 라는 라이브러리 역시 수정해줘야 했어요. 여기서 주의할 건 notion공식 라이브러리가 아니에요! 공식라이브러리 이름은 notionhq에요!node_modules/notion-utils/
notion-utils라이브러리 내부에
defaultMapImageUrl 함수에서 .gif일 경우에 url원본을 바로 리턴할 수 있게 해줘야 되요. 수정을 다했으면 patch-package로 수정사항을 commit해야되요. ( patch-package너무 좋은거 같아요.)
수정완료후 패치 명령어
JavaScript
> npx patch-package react-notion-x
> npx patch-package notion-utils패치를 완료하면 아래와 같이 패치파일이 생겨요.
앞으로 빌드할 때 버전을 유지하면서 커스터 마이징된 버전을 사용할 수 있게 해준답니다.
렌더링 완성까지의 과정
위의 방법으로 인해
gif짤림 현상은 해결이 되었지만, 그게 끝이 아니였어요. 해당 코드를 가지고 사용자 경험을 끌어올릴 수 있는 방법을 찾아야 했죠. 첫 번째 방법: generateStaticProps로 전체 페이지 캐싱
처음에는 무작정 전체 페이지를 캐싱해보기로 했었어요.
확실히 전체 페이지를 캐싱하니까, 즉각적인 반응속도 때문에 너무나도 만족스러웠어요. 생각보다 디테일 페이지 구현이 빨리 끝났다고 좋아하고 있었죠. 하지만 문제는 그 다음날 일어났어요. 캐싱된 gif는 만료시간이 존재했기 때문에 그 다음날 열어보니
gif이미지에 접근할 수 없었어요두 번째 방법 : CSR
두 번째 방법을 선택할 때 고민이 되었던 것은 다음과 같았어요.
- gif를 내부 라우트 핸들러로 요청하게끔 url을 수정해서 사용하자.
- 매번 새로 url를 요청해서 사용하도록 CSR로 가자
첫 번째 옵션으로 gif를 내부 라우터로 요청하게끔 하자는 뜻은,
app/api/image-proxy 같은 라우트 핸들러를 만들어서 사용한다는 뜻이죠. 추가적으로 storage를 운용하겠다는 뜻이에요. 