」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 【個人網站】Next如何整合Notion資料庫

【個人網站】Next如何整合Notion資料庫

發佈於2024-11-07
瀏覽:971

To integrate a Notion database into a Next.js project, you can use Notion as a content management system (CMS) and display its content on your website. Below is a simple step-by-step guide to help you integrate the Notion database into Next.js.

Basic Preparation

Obtain Notion API Key and Database ID

  1. Get Notion API Key: Go to the Notion Developer Portal and create a new integration. Once created, you will receive an API key.
  2. Get Database ID: Navigate to the Notion database you want to integrate, and copy the URL of the database page. The database ID is the string of characters between https://www.notion.so/ and ?v= in the URL.

[Personal Website] How to Integrate Notion Database in Next

还不清楚的可以看上篇文章:[01.[个人网站]如何使用Notion作为数据库进行全栈开发]

Using the Official SDK

Step 1: Install Dependencies

First, you need to install Notion’s official SDK, @notionhq/client, to communicate with the Notion API. You can install it using npm or yarn:

npm install @notionhq/client
# or
yarn add @notionhq/client

Step 2: Set up the Notion Client

Create a file lib/notion.js in the root directory of your Next.js project and configure the Notion client as follows:

// lib/notion.js
import { Client } from '@notionhq/client';

const notion = new Client({
  auth: process.env.NOTION_API_KEY,
});

export const getDatabase = async (databaseId) => {
  const response = await notion.databases.query({ database_id: databaseId });
  return response.results;
};

Make sure to store your Notion API key in an environment variable NOTION_API_KEY.(.env.local)

NOTION_API_KEY=your_secret_api_key

Step 3: Fetch Database Content

In your Next.js page, you can use getStaticProps or getServerSideProps to fetch the content from the Notion database.

// pages/index.js
import { getDatabase } from '../lib/notion';

export const getStaticProps = async () => {
  const databaseId = process.env.NOTION_DATABASE_ID;
  const posts = await getDatabase(databaseId);

  return {
    props: {
      posts,
    },
    revalidate: 1, // ISR (Incremental Static Regeneration)
  };
};

export default function Home({ posts }) {
  return (
    

My Notion Blog

    {posts.map((post) => (
  • {post.properties.Name.title[0].plain_text}
  • ))}
); }

Ensure that you have your Notion database ID stored in NOTION_DATABASE_ID as an environment variable.

NOTION_DATABASE_ID=your_database_id

Step 4: Deployment and Verification

Finally, deploy your Next.js project to Vercel or another platform, and verify that you can successfully fetch and display data from the Notion database.

Additional Tips

  • You can display different properties of Notion pages (like Name, Tags, Date, etc.) on your page.
  • Consider using getServerSideProps to fetch data on every request or use getStaticProps with ISR (Incremental Static Regeneration) to optimize performance.

By following these steps, you can successfully integrate a Notion database into your Next.js project and use it to manage and display content.

Using Notion API with Wrapped URLs

Lastly, I ended up using this method for convenience when integrating with the react-notion-x components.

Make sure to add your Notion Database ID and Notion API Key to the .env.local file:

NOTION_DATABASE_ID=your_database_id
NOTION_API_KEY=your_secret_api_key

Step 1: Install the necessary dependencies

First, install the necessary dependencies such as notion-types, notion-utils, and got to handle requests to the Notion API.

npm install notion-types notion-utils got p-map

Step 2: Create a file to encapsulate Notion API interactions

In your Next.js project, create a file, for example, lib/NotionAPI.ts, which will encapsulate interactions with the Notion API. This file will contain methods for calling Notion API endpoints to fetch data from pages and collections.

// lib/NotionAPI.ts
import * as notion from "notion-types";
import got, { OptionsOfJSONResponseBody } from "got";
import {
  getBlockCollectionId,
  getPageContentBlockIds,
  parsePageId,
  uuidToId,
} from "notion-utils";
import pMap from "p-map";

// 定义权限记录接口
export interface SignedUrlRequest {
  permissionRecord: PermissionRecord;
  url: string;
}

export interface PermissionRecord {
  table: string;
  id: notion.ID;
}

export interface SignedUrlResponse {
  signedUrls: string[];
}

// 定义NotionAPI类
export class NotionAPI {
  private readonly _apiBaseUrl: string;
  private readonly _authToken?: string;
  private readonly _activeUser?: string;
  private readonly _userTimeZone: string;

  constructor({
    apiBaseUrl = "",
    authToken,
    activeUser,
    userTimeZone = "America/New_York",
  }: {
    apiBaseUrl?: string;
    authToken?: string;
    userLocale?: string;
    userTimeZone?: string;
    activeUser?: string;
  } = {}) {
    this._apiBaseUrl = apiBaseUrl;
    this._authToken = authToken;
    this._activeUser = activeUser;
    this._userTimeZone = userTimeZone;
  }

  // 获取页面内容
  public async getPage(
    pageId: string,
    {
      concurrency = 3,
      fetchMissingBlocks = true,
      fetchCollections = true,
      signFileUrls = true,
      chunkLimit = 100,
      chunkNumber = 0,
      gotOptions,
    }: {
      concurrency?: number;
      fetchMissingBlocks?: boolean;
      fetchCollections?: boolean;
      signFileUrls?: boolean;
      chunkLimit?: number;
      chunkNumber?: number;
      gotOptions?: OptionsOfJSONResponseBody;
    } = {}
  ): Promise {
    const page = await this.getPageRaw(pageId, {
      chunkLimit,
      chunkNumber,
      gotOptions,
    });

    const recordMap = page?.recordMap as notion.ExtendedRecordMap;

    if (!recordMap?.block) {
      throw new Error(`Notion page not found "${uuidToId(pageId)}"`);
    }

    recordMap.collection = recordMap.collection ?? {};
    recordMap.collection_view = recordMap.collection_view ?? {};
    recordMap.notion_user = recordMap.notion_user ?? {};
    recordMap.collection_query = {};
    recordMap.signed_urls = {};

    if (fetchMissingBlocks) {
      while (true) {
        const pendingBlockIds = getPageContentBlockIds(recordMap).filter(
          (id) => !recordMap.block[id]
        );

        if (!pendingBlockIds.length) {
          break;
        }

        const newBlocks = await this.getBlocks(
          pendingBlockIds,
          gotOptions
        ).then((res) => res.recordMap.block);

        recordMap.block = { ...recordMap.block, ...newBlocks };
      }
    }

    const contentBlockIds = getPageContentBlockIds(recordMap);

    if (fetchCollections) {
      const allCollectionInstances: Array = contentBlockIds.flatMap((blockId) => {
        const block = recordMap.block[blockId].value;
        const collectionId =
          block &&
          (block.type === "collection_view" ||
            block.type === "collection_view_page") &&
          getBlockCollectionId(block, recordMap);

        if (collectionId) {
          return block.view_ids?.map((collectionViewId) => ({
            collectionId,
            collectionViewId,
          }));
        } else {
          return [];
        }
      });

      await pMap(
        allCollectionInstances,
        async (collectionInstance) => {
          const { collectionId, collectionViewId } = collectionInstance;
          const collectionView =
            recordMap.collection_view[collectionViewId]?.value;

          try {
            const collectionData = await this.getCollectionData(
              collectionId,
              collectionViewId,
              collectionView,
              {
                gotOptions,
              }
            );

            recordMap.block = {
              ...recordMap.block,
              ...collectionData.recordMap.block,
            };

            recordMap.collection = {
              ...recordMap.collection,
              ...collectionData.recordMap.collection,
            };

            recordMap.collection_view = {
              ...recordMap.collection_view,
              ...collectionData.recordMap.collection_view,
            };

            recordMap.notion_user = {
              ...recordMap.notion_user,
              ...collectionData.recordMap.notion_user,
            };

            recordMap.collection_query![collectionId] = {
              ...recordMap.collection_query![collectionId],
              [collectionViewId]: (collectionData.result as any)
                ?.reducerResults,
            };
          } catch (err: any) {
            console.warn(
              "NotionAPI collectionQuery error",
              pageId,
              err.message
            );
          }
        },
        {
          concurrency,
        }
      );
    }

    if (signFileUrls) {
      await this.addSignedUrls({ recordMap, contentBlockIds, gotOptions });
    }

    return recordMap;
  }

  public async addSignedUrls({
    recordMap,
    contentBlockIds,
    gotOptions = {},
  }: {
    recordMap: notion.ExtendedRecordMap;
    contentBlockIds?: string[];
    gotOptions?: OptionsOfJSONResponseBody;
  }) {
    recordMap.signed_urls = {};

    if (!contentBlockIds) {
      contentBlockIds = getPageContentBlockIds(recordMap);
    }

    const allFileInstances = contentBlockIds.flatMap((blockId) => {
      const block = recordMap.block[blockId]?.value;

      if (
        block &&
        (block.type === "pdf" ||
          block.type === "audio" ||
          (block.type === "image" && block.file_ids?.length) ||
          block.type === "video" ||
          block.type === "file" ||
          block.type === "page")
      ) {
        const source =
          block.type === "page"
            ? block.format?.page_cover
            : block.properties?.source?.[0]?.[0];

        if (source) {
          if (!source.includes("secure.notion-static.com")) {
            return [];
          }

          return {
            permissionRecord: {
              table: "block",
              id: block.id,
            },
            url: source,
          };
        }
      }

      return [];
    });

    if (allFileInstances.length > 0) {
      try {
        const { signedUrls } = await this.getSignedFileUrls(
          allFileInstances,
          gotOptions
        );

        if (signedUrls.length === allFileInstances.length) {
          for (let i = 0; i  {
    const parsedPageId = parsePageId(pageId);

    if (!parsedPageId) {
      throw new Error(`invalid notion pageId "${pageId}"`);
    }

    const body = {
      pageId: parsedPageId,
      limit: chunkLimit,
      chunkNumber: chunkNumber,
      cursor: { stack: [] },
      verticalColumns: false,
    };

    return this.fetch({
      endpoint: "loadPageChunk",
      body,
      gotOptions,
    });
  }

  public async getCollectionData(
    collectionId: string,
    collectionViewId: string,
    collectionView?: any,
    {
      limit = 9999,
      searchQuery = "",
      userTimeZone = this._userTimeZone,
      loadContentCover = true,
      gotOptions,
    }: {
      limit?: number;
      searchQuery?: string;
      userTimeZone?: string;
      loadContentCover?: boolean;
      gotOptions?: OptionsOfJSONResponseBody;
    } = {}
  ) {
    const type = collectionView?.type;

    const isBoardType = type === "board";
    const groupBy = isBoardType
      ? collectionView?.format?.board_columns_by
      : collectionView?.format?.collection_group_by;

    let filters = [];
    if (collectionView?.format?.property_filters) {
      filters = collectionView.format?.property_filters.map(
        (filterObj

: any) => ({
          property: filterObj?.property,
          filter: {
            operator: "and",
            filters: filterObj?.filter?.filters,
          },
        })
      );
    }

    const body = {
      collection: {
        id: collectionId,
      },
      collectionView: {
        id: collectionViewId,
      },
      loader: {
        type: "reducer",
        reducers: {
          collection_group_results: {
            type: "results",
            limit,
            loadContentCover,
          },
        },
        userTimeZone,
        limit,
        loadContentCover,
        searchQuery,
        userLocale: "en",
        ...(filters.length > 0 ? { filters } : {}),
        ...(groupBy
          ? {
              groupBy,
            }
          : {}),
      },
    };

    return this.fetch({
      endpoint: "queryCollection",
      body,
      gotOptions,
    });
  }

  private async fetch({
    endpoint,
    body,
    gotOptions,
  }: {
    endpoint: string;
    body: unknown;
    gotOptions?: OptionsOfJSONResponseBody;
  }) {
    const url = `${this._apiBaseUrl}/${endpoint}`;
    const json = true;
    const method = "POST";

    const headers: Record = {
      "Content-Type": "application/json",
    };

    if (this._authToken) {
      headers.cookie = `token_v2=${this._authToken}`;
    }

    if (this._activeUser) {
      headers["x-notion-active-user-header"] = this._activeUser;
    }

    try {
      const res = await got.post(url, {
        ...gotOptions,
        json,
        method,
        body,
        headers,
      });

      return res.body as R;
    } catch (err) {
      console.error(`NotionAPI error: ${err.message}`);
      throw err;
    }
  }

  private async getSignedFileUrls(
    urls: SignedUrlRequest[],
    gotOptions?: OptionsOfJSONResponseBody
  ): Promise {
    return this.fetch({
      endpoint: "getSignedFileUrls",
      body: { urls },
      gotOptions,
    });
  }

  private async getBlocks(
    blockIds: string[],
    gotOptions?: OptionsOfJSONResponseBody
  ): Promise {
    return this.fetch({
      endpoint: "syncRecordValues",
      body: {
        requests: blockIds.map((blockId) => ({
          id: blockId,
          table: "block",
          version: -1,
        })),
      },
      gotOptions,
    });
  }
}

Step 3: Use the Encapsulated API in Next.js Pages

To fetch Notion database content in your Next.js pages, you can use the encapsulated NotionAPI class and pass the retrieved data to react-notion-x components for rendering.

// pages/[pageId].tsx
import { GetServerSideProps } from 'next';
import { NotionAPI } from '../lib/NotionAPI';
import { NotionRenderer } from 'react-notion-x';
import 'react-notion-x/src/styles.css';

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { pageId } = context.params;
  const notion = new NotionAPI();
  const recordMap = await notion.getPage(pageId as string);

  return {
    props: {
      recordMap,
    },
  };
};

const NotionPage = ({ recordMap }) => {
  return ;
};

export default NotionPage;

Step 4: Configure Routing in Next.js

To ensure that the [pageId].tsx file can dynamically render different Notion pages, you need to set up dynamic routing in Next.js. This will allow you to match the pageId parameter and fetch the corresponding Notion page content.

Here’s how you can configure dynamic routes:

  1. Create a Dynamic Route File in the pages directory:

In your pages directory, create a new file called [pageId].tsx:

pages/[pageId].tsx
  1. Implement Dynamic Page Rendering:

In the [pageId].tsx file, fetch the Notion content based on the pageId from the URL and render it dynamically.

Example code:

import { GetStaticProps, GetStaticPaths } from 'next';
import { NotionRenderer } from 'react-notion-x';
import { getNotionPageData } from '../lib/NotionAPI';
import 'react-notion-x/src/styles.css';

export default function NotionPage({ pageData }) {
  return (
    
); } export const getStaticProps: GetStaticProps = async ({ params }) => { const { pageId } = params!; const pageData = await getNotionPageData(pageId as string); return { props: { pageData, }, revalidate: 10, // Revalidate content every 10 seconds (ISR) }; }; export const getStaticPaths: GetStaticPaths = async () => { return { paths: [], // We’ll use fallback to handle dynamic routes fallback: 'blocking', // Generate pages on the fly if not pre-rendered }; };
  1. Explanation:
  2. getStaticPaths: Since your content is dynamic, we return an empty array for paths and set fallback: 'blocking' to generate pages on demand.
  3. getStaticProps: Fetches the Notion page content based on the pageId passed in the URL.

Summary

By following the steps above, you can now encapsulate Notion API requests and render Notion pages dynamically in your Next.js project using react-notion-x. This setup allows you to efficiently integrate Notion as a CMS while ensuring scalability and maintainability in your Next.js application.

版本聲明 本文轉載於:https://dev.to/jessie_chen/personal-website-how-to-integrate-notion-database-in-next-2i58?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何使用Golang高效取得Windows中的可用磁碟機清單?
    如何使用Golang高效取得Windows中的可用磁碟機清單?
    使用Golang 獲取Windows 上的驅動器清單尋求一種更有效的方法來在Windows 系統上的所有驅動器中搜尋特定的磁碟機檔案類型,Go 程式設計師可能想知道是否可以在沒有使用者指定輸入的情況下自動取得可用磁碟機清單。 使用 GetLogicalDrives 和位元操作的解決方案:To列出 W...
    程式設計 發佈於2024-11-07
  • 如何從 C++ 中的函數安全地傳回數組?
    如何從 C++ 中的函數安全地傳回數組?
    從C 中的函數傳回陣列嘗試從C 中的函數傳回陣列可能會導致意外行為,如以下程式碼所示片段:int* uni(int *a,int *b) { int c[10]; ... return c; }此函數嘗試從函數傳回本機陣列 c。但是,當函數返回時,數組佔用的記憶體將被釋放,從...
    程式設計 發佈於2024-11-07
  • HTML5中如何將表格屬性遷移到CSS?
    HTML5中如何將表格屬性遷移到CSS?
    HTML5 表格屬性轉換Visual Studio 的HTML5 驗證功能將單元格填充、單元格間距、valign 和對齊屬性標識為對表格元素無效。為了解決這個問題,HTML5 引入了 CSS 替代方案來控製表格的外觀和間距。 單元格填充:將 cellpadding 替換為應用於表格單元格的 CSS ...
    程式設計 發佈於2024-11-07
  • 為什麼不能在 C++ 中使用非常量變數來定義數組大小?
    為什麼不能在 C++ 中使用非常量變數來定義數組大小?
    了解使用 Const Int 作為數組大小的限制在 C 中,使用 const int 作為數組大小受到以下限制某些限制。讓我們深入研究為什麼會有這些限制。 考慮以下場景:允許:const int size = 2; int array[size] = {0}; // Compilation succ...
    程式設計 發佈於2024-11-07
  • 如何在 MySQL WHERE 子句中指定不明確的「user_id」欄位?
    如何在 MySQL WHERE 子句中指定不明確的「user_id」欄位?
    解決MySQL 'user_id' WHERE 子句中的歧義當處理共享列名的多個表時,MySQL 在使用時可能會遇到歧義WHERE 子句中的該列。這在錯誤訊息“user_id”中很明顯,其中 where 子句不明確。 考慮範例查詢:SELECT user.*, user_info.*...
    程式設計 發佈於2024-11-07
  • 如何從 Pandas 中的函數有效率地建立多個新列?
    如何從 Pandas 中的函數有效率地建立多個新列?
    使用Pandas 從函數建立多個新列在Pandas 中,您可能會遇到需要根據應用於某個函數的自訂函數建立多個新列的情況現有專欄。該任務可能看起來很簡單,但由於函數的預期返回類型,可能會出現意想不到的挑戰。 原始方法:指派給索引範圍最初,您可能會嘗試指派使用df.ix[: ,10:16] = df.t...
    程式設計 發佈於2024-11-07
  • 如何在 Go 中迭代地從切片中刪除元素?
    如何在 Go 中迭代地從切片中刪除元素?
    迭代地從切片中刪除元素迭代切片時,由於移動而在循環中刪除元素可能會很棘手後續元素。常見的錯誤方法是使用追加來刪除元素,如下例所示:a := []string{"abc", "bbc", "aaa", "aoi", &qu...
    程式設計 發佈於2024-11-07
  • 如何有效防止 C++ 中的記憶體洩漏:綜合指南
    如何有效防止 C++ 中的記憶體洩漏:綜合指南
    避免C 中的內存洩漏:綜合指南C 內存管理可能是一項艱鉅的任務,潛在的內存洩漏潛伏在每個角落。為了減輕這些風險,必須遵循一組最佳實踐。 防止記憶體洩漏的一般技巧了解所有權: 確定誰負責釋放記憶體。實施適當的清理機制以及時釋放分配的資源。 使用智慧指針:考慮使用智慧指針,例如 std::unique_...
    程式設計 發佈於2024-11-07
  • 使用html css和js的動畫進行冒泡排序
    使用html css和js的動畫進行冒泡排序
    代碼 : <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=d...
    程式設計 發佈於2024-11-07
  • 理解 Go eBPF:深入探討高效的核心級編程
    理解 Go eBPF:深入探討高效的核心級編程
    扩展伯克利数据包过滤器 (eBPF) 彻底改变了 Linux 内核可观察性、性能监控和安全性。 eBPF 允许开发人员直接在内核中运行沙盒程序,而无需修改内核代码,从而释放有效监视、跟踪和操作数据的能力。与以其简单性、并发性和强大生态系统而闻名的 Go ebpf 编程语言相结合,eBPF 成为构建...
    程式設計 發佈於2024-11-07
  • 為什麼 `__init__` 方法對於 Python 類別至關重要?
    為什麼 `__init__` 方法對於 Python 類別至關重要?
    為什麼在 Python 類別中使用 init? 在 Python 中初始化類別是一個基本概念,它允許您建立具有特定屬性和行為的類別的實例。 init方法充當類別的建構函數,為新物件提供初始化和配置。 理解類別和物件這很重要區分類(物件的藍圖)和物件(這些類別的實例)。類別定義其物件將繼承的屬性和方法...
    程式設計 發佈於2024-11-07
  • NextJS 應用程式的 Docker 和 Docker-Compose 最佳實務。
    NextJS 應用程式的 Docker 和 Docker-Compose 最佳實務。
    Best Practices of Docker & Docker-Compose for NextJS application. To create an optimized Dockerfile for a Next.js 14 application that sup...
    程式設計 發佈於2024-11-07
  • 資料結構:建立自訂節點類
    資料結構:建立自訂節點類
    作为一名开发人员,掌握数据结构是一项至关重要的技能,可以释放您解决问题的潜力。虽然 Java 中的标准集合框架提供了坚实的基础,但有时您需要超越内置数据结构并创建自己的自定义解决方案。 在这篇文章中,我们将学习如何创建自定义节点类以及它们如何帮助您有效地解决各种问题。 DATA STRUCTURE...
    程式設計 發佈於2024-11-07
  • 透過專家免費課程掌握編程
    透過專家免費課程掌握編程
    歡迎來到我的 Udemy 個人資料!如果您對程式設計充滿熱情並渴望提高自己的技能,那麼您來對地方了。我設計了適合初學者和高級學習者的課程,確保每節課都充滿實踐知識和實踐經驗。 您可以期待什麼: 內容全面:從基本程式設計概念到進階演算法,我的課程涵蓋廣泛的主題,適合各個級別。 實踐項目:透過實際專...
    程式設計 發佈於2024-11-07
  • 瓦納卡aa eyyyy
    瓦納卡aa eyyyy
    `` 這是我網站的頂部欄或所謂的導覽列。 但事實就是如此。 我們會沒事的吧? Ul 檢查。 const paymentMethods = [ { supportedMethods: 'basic-card', data: { ...
    程式設計 發佈於2024-11-07

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3