Notion API로 랜딩 만들기
“노션으로 랜딩 블로그, 고객 문의를 관리하고 싶어요.” 노션을 통해 통합 관리하고 싶은 분들이 읽으면 좋은 글.
노션을 좋아합니다. 저를 비롯해 저희 팀이 제일 많은 시간을 보내는 프로그램이기도 합니다. 노션으로 문서를 작성하고, 노션으로 일정을 관리하고, 노션으로 목표를 확인하죠. 그래서 랜딩에 배포하고 있는 블로그도 노션으로 관리하고 싶었습니다. 그래서 노션 API를 통해 블로그와 도입문의를 만들었습니다. 오늘은 노션API로 블로그, 고객문의를 구축했을 때 장점과 단점 그리고 방법에 대해 들려드리겠습니다.
안녕하세요. featuring의 프로덕트 디자이너 아이브입니다. 이번에 랜딩 블로그와 도입문의 쪽에 Notion API를 연동하여 랜딩페이지를 개발했습니다. 아래 영상은 그 결과물 중 하나로 랜딩에서 문의시 자동으로 노션에 쌓이는 구조입니다.

↑ 랜딩 고객 문의를 노션에 받는 기능
목차
1. Notion API 도입, 그 이전 그 이후

기존에 문제점이 크게 2가지 있었습니다.
- 블로그 작성
- 고객 (도입, 캠페인) 문의 관리
일단 제일 큰 문제는 블로그였습니다. 랜딩 페이지에 블로그 글을 올리려면 어드민 페이지를 통해 올려야합니다. 하지만 어드민 자체가 큰 리소스를 들여 만든 것이 아니여서 사용성이 너무 떨어졌습니다. 기본 어드민 에디터로 글을 쓰기가 너무 어려웠죠. 그래서 운영팀은 글을 작성할 때 노션을 활용했습니다. 노션에 글을 쓰고 다시금 어드민으로 옮기는 과정을 겪고 있었죠. 심지어 작성 중 글이 지워지는 등 다양한 문제를 겪고 있었습니다.
또한 고객 문의가 들어왔을 때도 문제는 마찬가지였습니다. 슬랙과 연동되어 바로바로 알림은 떴지만 정리는 또 노션에 따로 작성했습니다. 알림이 들어오면 고객과 전화를 한 뒤 상담 내용을 각 개인 페이지에 정리하는 식이었죠. 따로 정리하니까 리드 과정이 흩어져 고객관리가 어려워지는 지점이 생겼습니다.
그래서 저는 이문제를 Notion API를 통해 해결하기로 합니다. 목표는 Notion API를 통해 블로그 글을 작성하는 과정과 고객 문의 과정을 통합하여 한 번에 관리할 수 있게 만드는 것입니다.
결론부터 말씀드리자면 성공과 실패 사이 어딘가입니다. 이전보다는 확실히 편해졌지만 이상적으로 생각한 결과물은 아니였습니다. 기술적 한계가 불안정성 그리고 운영의 불편함으로 다가왔습니다.
- Notion API를 사용하려면 꼭 백엔드 서버가 필요하다. → cors 에러(보안의 이유로 통신 불가)를 마주하기 때문이다.(혹은 NextJS를 활용해 정적 생성하면 빌드 할 때 찌르기 때문에 필요없다) (혹은 Cloudflare로 서버를 대신할 수 있다.)
잘모름 - Notion API는 초당 3개의 요청까지만 받을 수 있다. → 고객 문의시 백엔드에서 로그를 쌓아야 안전하다. → 블로그를 정적 페이지로 미리 생성해야한다.
- 노션에 이미지를 직접 올리면 이미지 주소가 주기적으로 바뀐다.→ 페이지를 정적 생성하면 이미지 주소가 바뀌었을 때 이미지를 불러오지 못한다. → 어드민이나 다른 곳에 이미지를 업로드 후 그 이미지를 복사해서 노션에 붙여야한다.
아직까지 삽질하고 있지만 이 글을 읽고 제가 한 삽질 여러분은 다시 하지 않길 바랍니다…
2. Notion API 사용법
Notion API란? 노션 페이지나 블록, 데이터베이스를 읽어올 수도 있고 생성하고 변경할 수 있게 노션에서 제공하는 API입니다. 2021년 5월경에 베타 오픈을 했죠. 이번에 랜딩 페이지 개편하면서 Notion API를 활용하기로 했습니다. 노션에 통합 관리하면 운영 효율도 좋아질 거라 판단했기 때문입니다. 사용 방법은 꽤나 단순합니다. 아래 영상을 보면 자세히 나와있지만 글로도 간단하게 설명드리겠습니다.
Next.js 나만의 포트폴리오 사이트 만들기 (개발하는 김에 배포까지) / LottieFiles, Notion Api, TailwindCSS
↑ 제일 참고 많이한 영상
- Notion API 참고 자료노션 사용자의 98%가 이 기능을 모르고 있다!?↑ 백엔드 서버를 올리는 대신 Cloudflare를 썼다고 한다. 영어가 어렵고 로그를 쌓는 게 좋아보여서 포기했다.Postman으로 노션 API 확인하는 방법 (부제: 노션 API 사용법)
↑ 글 작성하면서 참고한 글. 사실 글은 잘 안봤다.
[Notion API와 함께 정적 페이지로의 여정 – 화해 블로그 기술 블로그](https://blog.hwahae.co.kr/all/tech/10960)
2.1. 노션 API 세팅
- Notion API 페이지를 들어간다.Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.
- API를 생성한다.

- 권한은 필요한대로 설정한다.

- 노션 페이지나 데이터베이스에 오른쪽 상단에 버튼을 클릭하여 API 연결하여 권한을 준다.

여기까지 벌써 준비가 끝났습니다.
이제 API요청만 하면 노션 페이지를 만들 수 있고 데이터베이스를 수정할 수 있게 되었습니다.
2.2. 노션 API 요청
Notion API 잘 설정되었는지 요청을 한 번 해보려면 백엔드 서버가 필요합니다. cors에러 때문에 서버에서 요청해야하죠. 하지만 저는 서버? 전혀 모르기 때문에 Postman을 활용해 확인했습니다. Postman은 개발된 API를 테스트할 수 있는 서비스인데 이걸 활용하면 손쉽게 확인할 수 있습니다.
- Notion API 문서에 들어간다.Start building with the Notion API
- 원하는 요청을 찾는다.저는 데이터베이스 어떤 식으로 구성되어있는지 확인하기 위해 GET요청을 보내봤습니다.

- Postman에 요청을 보내본다.
- 데이터베이스 Get요청 방법1) get 요청 주소를 database_id를 넣어 적는다.2) Auth 탭에서 Bearer Token에 내 Notion API 토큰을 추가해준다.
3) Headers 탭에서 노션 버젼을 적어 넣는다.

4) 요청한다.
- 데이터베이스 Get요청 방법1) get 요청 주소를 database_id를 넣어 적는다.2) Auth 탭에서 Bearer Token에 내 Notion API 토큰을 추가해준다.
- 응답값을 확인한다.노션 블록과 페이지는 저렇게 JSON 형태로 구성되어 있습니다. 이 요청을 받아서 화면을 구성하거나 화면에서 요청을 통해 노션에 글을 작성하면 됩니다.

3. NextJS와 함께 활용하기
이제는 실제 활용할 차례입니다. (NextJS, Typescript 사용)
3.1 고객 문의 만들기
랜딩 페이지에서 입력된 정보를 노션으로 전달해야 합니다.
- 받을 정보를 설계한다. 그리고 Postman get요청으로 설계된 정보를 확인한다.

- 그 다음 설계된 정보를 Body에 담아 Notion API를 요청한다.그리고 실패합니다. POST요청은 무조건 서버에서 보내야합니다.(TIP. 데이터베이스에 정보를 추가하고 싶으면 데이터베이스가 부모인 페이지로 생성해야합니다. 데이터베이스 열 하나하나가 페이지이기 때문입니다.)

- 백엔드 개발자분에게 cors에러 때문에 서버에서 Notion API에 요청할 수 있도록 만들어줄 것을 부탁한다.초당 3개의 제한이 있기 때문에 혹시 모르니 로그도 쌓아달라고 요청했습니다.요청하는 김에 슬랙 알림도 연결해달라고도 했습니다.

- 서버에 API정보를 담아 요청해보고 확인한다. 끝.
이렇게 만들어놓으니까 노션에서 고객 문의를 통합 관리할 수 있게 되었고 보드를 통해 문의 상태를 한번에 확인할 수 있어 좋았습니다. 그리고 생각보다 만들기 쉬웠습니다. 그러나 예상과 달리 백엔드가 꼭 필요했다는 점. 이 점에 유의하시길 바랍니다. ‘만약 혼자 서비스를 만든다’, ‘서버 없이 Notion API를 DB처럼 사용하려고 한다’ 라고 생각했으면 이 문제점을 잘 파악하고 시작해야 합니다. 해결 방법으로는 Cloudflare를 쓰는 방법이나 NextJS의 SSG를 이용하는 방법 등이 있습니다. (Cloudflare는 써본 적은 없습니다.)
3.2 블로그 만들기
블로그는 반대로 노션에 입력된 글을 랜딩 페이지에 전달하면 됩니다.
3.2.1 블로그 목록 만들기
일단 데이터베이스에서 글 목록을 긁어와 화면을 만들어야 합니다.
- 노션에 블로그 글을 등록할 데이터베이스를 만든다.

- 데이터베이스에서 게시완료 상태를 가진 글들을 랜딩페이지에 가져온다.그런데 이 때 문제가 있습니다. Notion API는 초당 3개의 요청만 가능하다는 거죠. 그래서 정적 페이지로 생성을 해야합니다. 저는 NextJS의 Static Site Generation를 사용해서 빌드할 때만 Notion API에 요청해 html 파일을 만들었습니다.SSG로 빌드하는 방법은 검색하면 자세히 나오지만 간단히 설명드리겠습니다. 일단 먼저 페이지 아래에 getStaticProps를 선언합니다. 이렇게 선언하면 빌드할 때 미리 API와 통신할 수 있습니다. 노션과 통신하는 방법은 아래 주석과 코드를 참고하시면 됩니다. (주석만 봐도 됩니다.)
export async function getStaticProps() { // Notion API에 보낼 정보 // header에는 토큰을 포함한 필수값 // body에는 sorting을 위한 선택값 const options = { method: 'POST', headers: { 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json', accept: 'application/json', authorization: `Bearer ${노션 토큰}` }, body: JSON.stringify({sorts:[{property: '발행일', direction:'descending'}]}) }; //옵션 정보를 담아 Notion API에 전달 const res = await fetch( 'https://api.notion.com/v1/databases/${노션 데이터베이스 ID}/query', options, ); const data = await res.json(); //revalidate는 페이지를 몇 초마다 자동 빌드할 건지 정하는 값이다. //선택값이지만 처음엔 없이 만들었더니 블로그 글을 쓸때마다 매번 배포해야해서 불편했다. return { props: { data }, revalidate: 600}; }; - 받은 json 데이터를 콘솔로 찍어보고 잘 꺼내서 화면을 그린다.
<Text> {item.properties.태그.multi_select && item.properties.태그.multi_select.map( ({ id, name, color }: Record<string, string>) => ( <Tag key={id} color={color}> {name} </Tag> ), )} <h4>{item.properties.제목.title[0].plain_text}</h4> <Date>{item.properties.발행일.date.start}</Date> </Text> - 그리고 각 페이지의 id값을 받아와 링크를 걸어주면 완성.

3.2.2 블로그 글 만들기
이제는 각 글들을 페이지로 만들 차례입니다. 게시 완료된 수에 맞춰 동적으로 페이지를 생성해야합니다.
- 일단 데이터베이스 안에 페이지를 생성해 글을 작성한다.

**[id].tsx동적 페이지 파일을 생성한다. 그리고 getStaticPath를 통해 생성할 페이지를 미리 알려준다.**export const getStaticPaths: GetStaticPaths = async () => { // ...아까처럼 Notion API로 데이터베이스의 게시완료된 글 목록을 불러온다. // 가져온 글 목록을 담아준다. const posts = await res.json() // 가져온 글의 총 갯수와 각 글의 아이디를 알려준다. const paths = posts.results.map((post: any) => { return { params: { id: post.id, }, }; }); //fallback 기능은 잘 몰라서 좋아보이는 거 적어넣었다. return { paths, fallback: 'blocking', }; };- 그 다음 getStaticProps로 페이지 안에 블록 정보를 캐온다.그런데 여기서 문제가 하나 더 있습니다. 블록은 한번 요청에 최대 100개 까지만 불러온다는 것입니다. 100개가 넘어갈 시
has_more속성이true가 되기 때문에has_more을 확인하여 반복문을 돌렸습니다. (이 부분은 어려워서 동료 프론트엔드 개발자분에게 도움을 요청했습니다 ㅎㅎ)// ...페이지의 ID, 페이지 타이틀 썸네일 받아오기 //페이지 안 블록 정보 받아오기 const pageDataRes = async (query?: string) => { const data = await fetch( `https://api.notion.com/v1/blocks/${pageID}/children${query ?? ''}`, options, ); return data.json(); }; //블록 정보를 일단 담기 let page_data = await pageDataRes(); //빈 변수를 선언 let blocks = []; //빈 변수에 받은 블록정보 담기 blocks = [...page_data.results]; //100개 넘어갈시 추가 요청 while (page_data.has_more) { const query = `?start_cursor=${page_data.next_cursor}`; page_data = await pageDataRes(query); blocks = [...blocks, ...page_data.results]; } return { props: { blocks, page, page_data }, revalidate: 600 }; - 받아온 블록 정보는 타입에 맞춰 화면에 그려준다.
↑ 자석 이모지가 있는 콜아웃을 버튼으로 만듬. 그리고 인용을 기존 콜아웃 박스처럼 박스로 출력. - 그런데 여기서 잠깐, 이미지 블록이 문제다.노션에 직접 업로드한 이미지의 경우 이미지 주소가 주기적으로 바뀌었습니다. 그래서 이미지가 안나왔죠. 특히나 revalidate 속성이 없었을 때는 빌드할 때 주소가 고정되기 때문에 하루가 지나면 이미지가 안나왔습니다. 이를 해결하기 위해 revalidate를 10분으로 설정해 10분마다 빌드하기로 설정했습니다. 하지만 이것도 10분 사이에 이미지가 안나옵니다. 또 노션의 규칙에 의존하기엔 불안정적이라 느껴서 아예 방법을 바꿨습니다. 기존 구축된 어드민에 따로 이미지를 업로드 하고 주소를 가져와 노션에 임베드하는 거죠. 이 지점이 실제 블로그 운영시 귀찮은 과정이긴 합니다.
(이건 어떻게 해결할까..?)
- 아무튼 이미지 블록까지 완성.

최종 결과물을 단순히 노션에 글을 작성하고 게시완료로 변경하면 끝입니다. 게시완료된 글만 랜딩 페이지에 노출되도록 만들었습니다. 노션 글 템플릿도 저희끼리 규칙을 만들었는데 빨간색 배경으로 된 블록은 출력하지 않기로 했습니다. 그래서 글 초고나 전략, 타겟 등을 빨간색 박스에 적어두고 작성할 수 있도록 했습니다. 아까 잠깐 말했듯이 자석 이모지가 달린 콜아웃은 버튼으로 출력하고 있습니다.
여기까지 Notion API를 이용해 고객 문의와 블로그를 구현해봤습니다. 구현하다보니 어려운 지점도 있었고 결과물도 아쉬운 부분이 있습니다. 나중에 개선한다면 이미지 올리는 부분을 개선하고 싶습니다. 또 SEO를 위해 키워드 속성을 추가해 메타태그에 키워드를 걸고 이미지 캡션을 alt 속성으로 입력할 예정입니다.
마지막 세줄 요약.
⏰시간이 없으신 스타트업분들을 위한 세줄 요약입니다.
- Notion API 써봤는데 좋습니다.
- 근데 만들어보니 아쉬운 점도 있습니다.
- 제 방법 참고해서 더 좋은 방법으로 활용하시길 바라겠습니다.
