Next.js의 App Router에서의 react-notion-x 라이브러리 적용기입니다.
+ 다국적 언어 지원 라이브러리인 react-i18next도 함께 사용하고 있습니다.
* 23.10.25 : 현재는 모노레포 구축 후 nextjs-notion-starter-kit으로의 라이브러리 교체가 되었습니다.
구현화면
이전 포스팅에서 언급한 것처럼 react-notion-x 라이브러리를 사용하여 노션의 데이터테이블 데이터를 가져왔다.
- 특히 다른 라이브러리와 비교해봤을 때 위의 멀티탭 기능이 제공된다는 점이 마음에 든다.
- 데이터 분류 기능도 그대로 가져오고 페이지 공간을 크게 줄일 수 있다!
의사코드 (코드 부분만)
1. SSR에서 루트 페이지 데이터를 fetching해온다. (Personal 계정을 위한 OAuth Token이 오고간다.)
2. 가져온 데이터를 recordMap 변수에 할당한다.
3. 라이브러리에서 제공해주는 NotionRenderer 컴포넌트를 import해와서 recordMap props를 전달하고, components에 Collection, Modal 등 DB UI에 필요한 요소들을 전달한다.
--- 여기까지 루트 페이지 구현 ---
4. 각 게시글을 클릭하면 들어가는 동적 라우팅 폴더를 만든 후, page.tsx를 생성한다.
(각 게시글을 클릭하면 /{페이지Id} 경로로 이동하여 세부 페이지 정보를 불러온다.)
5. 3번 과정을 반복! (따라서 3번을 공통 컴포넌트로 만든다.)
--- 여기까지 세부 페이지 구현 ---
사용법
각자 환경도 다르고 구현하고자 하는 기능도 천차만별이기 때문에 자세한 설명은 생략하고 큼직하게 언급하고 넘어갈 예정이다. 나머지는 링크 등을 참고하면 좋을 것 같다.
특히 1번은 노션 공식 문서에서 자세하게 안내가 되어 있다!
1. notion API integration 생성
- 링크 들어가서 API 통합 만들기
2. 노션 페이지 생성 후, 데이터베이스 만들기
- 내 경우에는 이렇게 멀티탭 데이터베이스로 만들었다.
3. 페이지 설정에서 1번에서 만든 API 통합하기
- 3번이 완료되면 아래와 같이 뜬다.
4. 환경변수 설정하기
1) 페이지 ID
아래와 같이 페이지 자체 ID를 긁어서 저장한다. (DB ID 아님)
2) 토큰
공식문서 보고 따라하면 절대 안 되는 게 react-notion-x를 사용하려면 공식에서 권장하는 OAuth 방식이 아니라, 예전 방식이 사용된다. 토큰 역시 쿠키에 보면 token_v2 키값으로 나와있는 값을 환경변수에 저장해준다.
5. SSR ) 최상단 페이지 데이터 불러오기
[라이브러리 설치 참고]
"react-notion-x": "^6.16.0", // 라이브러리
"notion-client": "^6.16.0", // OAuth를 위함
"notion-types": "^6.16.0", // TypeScript용
"notion-utils": "^6.16.0", // 나중에 SSR로 동적 메타태그 생성 용 (제목 fetching)
나는 메인페이지에서 노션 페이지를 노출시킬 것이기 때문에 가장 최상단 파일에서 데이터를 가져왔다.
app/[lng]/page.tsx
- 참고로 [lng]는 언어 동적 라우팅
import { NotionAPI } from 'notion-client';
import Intro from '@/components/common/effect/Intro';
import Loading from '@/components/common/loading/Loading';
import LngSwitchButtonSSR from '@/components/language-button/LngSwitchButtonSSR';
import Footer from '@/components/layouts/footer/Footer';
import NotionPage from '@/components/notion/NotionPages';
import { LngParamsProps } from '@/types/lngSwitch';
import styles from './page.module.scss';
export const notion = new NotionAPI({
authToken: process.env.NOTION_TOKEN_V2, // 1. OAuth 인증
});
const Home = async ({ params: { lng } }: LngParamsProps) => {
if (process.env.NOTION_PAGE_ID) {
try {
const recordMap = await notion.getPage(process.env.NOTION_PAGE_ID); // 2. 최상단 페이지 데이터 가져오기
return (
<div className={styles.container}>
<div className={styles.wrapper}>
.
.
<NotionPage recordMap={recordMap} /> // 3. 가져와서 NotionPage 컴포넌트로 값 전달
</div>
<Footer lng={lng} />
</div>
);
} catch (error) {
return console.error(error);
}
} else return <Loading />;
};
export default Home;
6. CSR) 공통 컴포넌트인 NotionPages 만들기
- 이 컴포넌트를 공통적으로 거쳐서 최종적으로 브라우저에 노션 데이터가 라이브러리에 세팅한 대로 출력이 된다.
- 필요한 요소들을 동적으로 가져와서 세팅해주면 된다.
@/components/notion/NotionPages.tsx
'use client'; // CSR
import dynamic from 'next/dynamic';
import Image from 'next/image';
import Link from 'next/link';
import { ExtendedRecordMap } from 'notion-types';
import 'react-notion-x/src/styles.css';
import { NotionRenderer } from 'react-notion-x';
interface NotionPageProps {
recordMap: ExtendedRecordMap;
}
export default function NotionPage({ recordMap }: NotionPageProps) {
const Code = dynamic(
() => import('react-notion-x/build/third-party/code').then((m) => m.Code),
{
ssr: false,
},
);
const Collection = dynamic(
() => import('react-notion-x/build/third-party/collection').then((m) => m.Collection),
{
ssr: false,
},
);
const Equation = dynamic(
() => import('react-notion-x/build/third-party/equation').then((m) => m.Equation),
{
ssr: false,
},
);
const Modal = dynamic(
() => import('react-notion-x/build/third-party/modal').then((m) => m.Modal),
{
ssr: false,
},
);
// fullPage는 노션의 헤더와 위계별 링크 기능까지 다 포함된 기능이다.
return (
<NotionRenderer
recordMap={recordMap}
fullPage
components={{
Code,
Collection,
Equation,
Modal,
nextImage: Image,
nextLink: Link,
}}
/>
);
}
~ 중간 결과 ~
여기까지 하면 최상위 노션 페이지 데이터가 그대로 브라우저에 그려진다!
6. SSR ) 상세 페이지 데이터 불러오기
- 그 다음은 각 게시글 제목을 클릭했을 때, 해당 페이지 데이터를 가져오면 끝이다.
- 4번에서 노션 최상단 페이지의 데이터를 가져온 방식과 거의 그대로 하면 된다.
달라진 점은, 라이브러리에서 자동으로 세부 페이지 경로를 각 세부 페이지 Id로 이동( 예 - localhost:3000/{pageId} )시키기 때문에 그에 맞춰서 폴더를 만들어주면 된다.
나 같은 경우에는 중간에 동적 라우팅 경로(lng)가 추가되어 있었기 때문에 폴더 경로는 아래와 같았다.
page.tsx를 만들어주고, params에서 pageId 값을 추출해서 해당 페이지 데이터 값을 fetching해온다.
app/[lng]/[pageId]/page.tsx
import { Metadata } from 'next';
import { getPageTitle } from 'notion-utils';
import Footer from '@/components/layouts/footer/Footer';
import { notion } from '../page';
import NotionEachPage from './NotionEachPage';
interface fetchEachPagesProps {
params: {
pageId: string; // pageId 추출
lng: string;
};
}
const fetchEachPages = async ({ params: { pageId, lng } }: fetchEachPagesProps) => {
try {
const recordMap = await notion.getPage(pageId); // 세부 페이지 데이터 가져오기
return <NotionPage recordMap={recordMap} />;
} catch (error) {
return console.error(error);
}
};
export default fetchEachPages;
8. 끝!
이제 클릭하면 아래와 같이 노션 상세페이지가 잘 출력되는 것을 확인할 수 있다.
주의
나 같은 경우는 데이터 베이스가 다른 데이터 베이스에 연결되어 있는 구조였기 때문에 아래와 같은 이슈가 생기며 화면이 보이지 않았다..! 다른 데이터 베이스 연결을 끊어주면 된다.
참고자료
https://github.com/NotionX/react-notion-x/blob/master/examples/full/components/NotionPage.tsx
https://github.com/NotionX/react-notion-x/issues?q=Notion+page+not+found
'📌 PROJECT > 2309 다국어 지원 포트폴리오' 카테고리의 다른 글
pnpm을 이용한 모노레포 마이그레이션 (0) | 2023.10.18 |
---|---|
NotionAPI로 블로그 만들기 (3) Next.js App Router 미들웨어로 redirection 설정하기 (0) | 2023.10.15 |
NotionAPI로 블로그 만들기 (1) nextjs-notion-starter-kit vs react-notion-x (0) | 2023.10.11 |
[CI/CD] lint-staged, husky로 린트 검사 자동화하기 (+Jest test) (0) | 2023.10.02 |
[react-typist] React 18 이상에서 호환 안 되는 문제 (0) | 2023.09.27 |