[react-notion-x] 노션 컨텐츠 홈페이지에서 불러오기 (feat.NEXT.JS)

2023. 6. 6. 13:00Library,Framework

728x90

이번 포스트에서는 노션 컨텐츠를 홈페이지에서 불러오는 방법에 대해 알아보도록 하겠습니다.

보통은 채용페이지에서 많이 쓰이는 방식이지만 필자는 설문 결과값에 따라 글들을 맞춤 추천해주는 용도로 사용했습니다. 

궁금하신분은 아래 링크로 이동하셔서 직접 해보시기를 추천드립니다.

 

 

서비스 신청 | 정리습관

집정리가 필요한 고객에게 정리전문가를 연결하고 정리습관을 만드는 대표 집정리 플랫폼 클린테크 기업

www.jungleehabit.com

 


iframe 으로는 안되나요?

우선 notion 컨텐츠를 사이트에서 보여주기만 하면 되는거면 별도의 패키지 설치 없이 페이지를 공개설정 해두고 iframe을 사용하면 안되나요 라고 물어보실 수 있겠지만, 결론 부터 말하자면 안됩니다.

 

Notion 자체적으로 iframe을 사용하지 못하도록 막아두었기 때문에 따로 가공해서 iframe 스니펫을 만들어주는 (대부분 유료) 도구들을 사용하지 않으면 정상적으로 페이지가 보이지 않습니다.

 


react-notion-x

이를 해결하기 위해서 react-notion-x 라는 notion 랜더러를 사용하면 됩니다. react-notion도 있지만 노션 컨텐츠가 복잡해 지원하지 않는 블록이 있다면 레이아웃이나 스타일링이 제대로 적용되지 않을 수 있으니 확장버전인 react-notion-x를 사용하도록 하겠습니다.

 

지원하는 블록과 패키지 구성을 확인하시고 싶으신분들은 아래 깃헙 레포를 참고해주시면 되겠습니다.

 

 

GitHub - NotionX/react-notion-x: Fast and accurate React renderer for Notion. TS batteries included. ⚡️

Fast and accurate React renderer for Notion. TS batteries included. ⚡️ - GitHub - NotionX/react-notion-x: Fast and accurate React renderer for Notion. TS batteries included. ⚡️

github.com

 

사용하고자 하는 프로젝트에 react-notion-x을 설치해주도록 합시다.

 

npm i react-notion-x

 

Notion 테마 스타일을 사용하기 위해선 아래와 같이 스타일 시트를 불러와주시면 됩니다.

 

 

// notion 테마 스타일링 (필수)
import 'react-notion-x/src/styles.css'

// 코드 하이라이트 스타일용 (선택)
import 'prismjs/themes/prism-tomorrow.css'

// 공식등 수학적 기호 스타일용 (선택)
import 'katex/dist/katex.min.css'

공식문서에서 react로 사용시 아래와 같이 사용하라고 하고 있습니다.

// 사용방법 (공식문서)

import { NotionAPI } from 'notion-client'
const notion = new NotionAPI()
const recordMap = await notion.getPage("노션 공개용ID")
import { NotionRenderer } from 'react-notion-x'

export default ({ recordMap }) => (
  <NotionRenderer recordMap={recordMap} fullPage={true} />
)

 

노션 공개용 ID는 notion에서 컨텐츠(포스트)를 공개설정 했을경우 notion.so 뒤에 나오는 유니크한 아이디 값입니다. 

 

notion.so/를 제외한 나머지 ID값

공개설정을 한뒤 해당 ID값을 넣어주게 되면 컨텐츠에 따라 제대로 보일수도 있고 안보일수도 있습니다.

그 이유는 해당 패키지의 경우 최대한 가볍게 만들기 위해서 기본적인 블록만 넣어두었고 컬렉션이나 , 모달 같은 비교적 무거운 컴포넌트를 기본으로 포함시키지 않았기 때문입니다.

 

만약 본인 프로젝트에 포함 시키고 싶은 노션 컨텐츠가 컬렉션 (맨 처음 이미지 맞춤 추천글 형식) 이나, 모달, 코드, PDF등을 포함하고 있다면 따로 import후 사용하시면 됩니다.

 

import { Code } from 'react-notion-x/build/third-party/code'
import { Collection } from 'react-notion-x/build/third-party/collection'
import { Equation } from 'react-notion-x/build/third-party/equation'
import { Modal } from 'react-notion-x/build/third-party/modal'
import { Pdf } from 'react-notion-x/build/third-party/pdf'

 

필자는 Collection, Modal을 사용하고 있기 때문에 두개를 불러왔고, 그 마저도 사용하지 않는 페이지가 있기 때문에 필요할때만 불러오도록 dynamic import를 사용했습니다.

 

const Collection = dynamic(() =>
  import('react-notion-x/build/third-party/collection').then(
    (m) => m.Collection
  )
)

const Modal = dynamic(
  () => import('react-notion-x/build/third-party/modal').then((m) => m.Modal),
  {
    ssr: false
  }
export default ({ recordMap }) => (
  <NotionRenderer
    recordMap={recordMap}
    components={{
      Collection,
      Modal,
    }}
  />
)

NEXT 프로젝트에 적용하기

기본적인 사용방법을 확인했으니 본격적으로 Next 프로젝트에 적용을 해보도록 하겠습니다. Notion 공개 ID값에 따라 각각 다른 컨텐츠를 보여줘야하기 때문에 동적 라우트 (Dynamic Route)를 설정해주겠습니다.

 

노션 컨텐츠의 내용 그리고 용도에 따라 어떤 방식으로 랜더링 할지는 달라지겠으나 필자의 경우 설문 결과값 페이지는 별로 많지도 않고(10개) 내용 또한 거의 변하지 않기 때문에 빌드타임에 페이지를 만들어 놓는 SSG 방식을 사용하여 Pre-rendering을 해주었습니다.

 

Pre-rendering에 대해 정보가 더 필요하신분은 아래 글을 참고해주시길 바랍니다.

 

 

[NEXT.js] 넥스트 JS를 배워보자 1편 - Pre Rendering

예전부터 꼭 배워보고 싶은 Next JS 프레임워크에 드디어 입문했다. 여태까지 관리자 페이지나 랜딩페이지를 만들다 보니 CSR(Client Side Rendering)로도 충분했지만 현재 SEO과 원할하게 이루어져야 하

mingeesuh.tistory.com

 

하나하나 설명하는것보다 보면서 이해하는게 쉬울거라고 생각해서 아래 코드와 주석을 첨삭했습니다.

 

pages/survey/result/[id].js

import React from "react";
import "react-notion-x/src/styles.css";
import { useRouter } from "next/router";
import { getAllPagesInSpace } from "notion-utils";
import { defaultMapPageUrl } from "react-notion-x";
import { NotionAPI } from "notion-client";
import { Collection } from "react-notion-x/build/third-party/collection";
import { Modal } from "react-notion-x/build/third-party/modal";
import { NotionRenderer } from "react-notion-x";
import Image from "next/image";
import Link from "next/link";
const notion = new NotionAPI();

const NotionResult = ({ recordMap }) => {
  const router = useRouter();
  const { id } = router.query; // notion 공개용 ID

  return (
      <div>
          <NotionRenderer
            disableHeader // notion 헤더 안보이도록
            components={{
              Collection,
              Modal,
              nextImage: Image // Next 이미지 (optimization) 사용하고 싶을 경우 해당 컴포넌트 전달
            }} // 사용할 컴포넌트들
            recordMap={recordMap}
            fullPage={true} // 전체 페이지 설정
            mapPageUrl={(pageId) => `/survey/result/${pageId}`} // 중첩 포스트 컬렉션들 클릭시 해당 url로 이동 되도록 설정
            rootPageId={id} // 첫 root 페이지 ID
          />
      </div>

      
  );
};

 // 필요시 revalidate를 통해 업데이트 하도록 설정 가능
 // 필자의 경우 업데이트가 자주 일어나지 않는 페이지이기 때문에 따로 추가하지 않음
export async function getStaticProps(context) {
  const recordMap = await notion.getPage(
    `https://notion-api.splitbee.io/v1/page/${context.params.id}`
  );
  return {
    props: {
      recordMap,
    },
  };
}

// 관련된 모든 노션 컨텐츠들의 notion 공개용 ID를 불러와 경로를 설정
// PUBLIC_NOTION_ID는 모든 관련 컨텐츠의 최상위 문서 ID를 넣어주면 된다. 
// 다 페이지가 따로따로일 경우는 notion에서 페이지를 미리 연결 시키놓도록 하자
export async function getStaticPaths() {
  const mapPageUrl = defaultMapPageUrl(process.env.NEXT_PUBLIC_NOTION_ID);
  const pages = await getAllPagesInSpace(
    process.env.NEXT_PUBLIC_NOTION_ID,
    null,
    notion.getPage,
    {
      traverseCollections: false,
    }
  );

  const paths = Object.keys(pages)
    .map((pageId) => mapPageUrl(pageId))
    .filter((path) => path && path !== "/");
	
  // fallback 설정을 true로 해줌으로써 혹시 전 단계에서 경로를 세팅하지 못했더라도 불러올수 있도록 설정
  return {
    paths,
    fallback: true,
  };
}

export default NotionResult;

 

 

NEXT 웹앱에 적용된 모습