01.【个人网站】使用 Notion 数据库结合 Next.js 全栈开发

354 阅读6分钟

在这篇文章中,我将展示如何使用 Next.js 和 Notion API 构建一个个人旅行博客,并将其部署到 Vercel。最终目标是让博客在自定义域名 jessieontheroad.com 上运行。

git源码:github.com/Jessie-jzn/… 网站如下:www.jessieontheroad.com/

image.png

前提条件

在开始之前,你需要确保已经完成以下几步:

  1. 购买域名:在域名注册商处购买一个域名,例如 jessieontheroad.com
  2. Vercel 账号:注册一个 Vercel 账户。
  3. Notion API Token:获取一个 Notion API 令牌,用于访问 Notion 数据库。

Notion相关

要使用Notion作为你的旅游攻略网站的内容管理系统,你需要先在Notion中配置数据库,并获取必要的API密钥和数据库ID。

配置步骤

步骤 1:创建Notion数据库

  1. 创建新页面

    • 打开Notion,点击左侧菜单中的“New Page”创建一个新页面。
  2. 添加数据库

    • 在新页面中,选择“Table”来创建一个新的数据库。
    • 命名这个数据库为“Travel Guides”。
  3. 配置数据库列

    • 添加以下列来存储旅游攻略的相关信息:

      • Title:攻略的标题(默认已经有Title列)。
      • Description:攻略的简短描述(类型为Text)。
      • Content:攻略的详细内容(类型为Text)。
      • Location:攻略涉及的地点(类型为Text)。
      • Image:攻略的封面图片(类型为Files & media)。
      • Published Date:发布日期(类型为Date)。
      • Author:作者(类型为Text)。

步骤 2:获取Notion API密钥

  1. 访问Notion Integration

  2. 创建新集成

    • 点击“New integration”按钮。
    • 填写集成的名称,例如“Travel Guide Website”。
    • 选择工作区。
    • 提交后,你将获得一个“Internal Integration Token”。将此Token保存下来,用于后续API调用。

步骤 3:共享数据库给集成

  1. 打开你的Notion数据库页面
  2. 点击右上角的“Share”按钮
  3. 在“Invite”字段中,输入刚刚创建的集成名称,然后选择它。或者直接用connect中,找到刚刚创建的集成名称
  4. 点击“Invite”,将数据库权限赋予该集成,

步骤 4:获取数据库ID

  1. 打开你的Notion数据库页面

  2. 从浏览器地址栏中复制数据库ID

Next集成Notion

代码步骤

步骤1:初始化 Next.js 项目

首先,创建一个新的 Next.js 项目。如果你还没有安装 Next.js,可以通过以下命令安装:

npx create-next-app@latest jessie-travel-blog
cd jessie-travel-blog

步骤2:添加 Tailwind CSS

安装 Tailwind CSS 并进行配置:

npm install tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

tailwind.config.js 中配置内容选项:

/** @type {import('tailwindcss').Config} */
const { fontFamily } = require('tailwindcss/defaultTheme');
const colors = require('tailwindcss/colors');

module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  darkMode: 'class',
  plugins: [],
};

styles/globals.css 中导入 Tailwind CSS:

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --notion-max-width: 100%;
  --notion-header-height: 45px;
}

步骤2:设置 Notion API

在你的项目中创建一个文件夹 lib,并在其中创建一个文件 notion.js。这个文件将包含与 Notion API 交互的代码。

// lib/notion/NotionServer.ts
import { Client } from '@notionhq/client';
import { NotionAPI } from 'notion-client';
import { QueryDatabaseResponse } from '@notionhq/client/build/src/api-endpoints';

import { NOTION_TOKEN } from '../constants';
if (!NOTION_TOKEN) {
  throw new Error('NOTION_TOKEN is not defined');
}

class NotionService {
  private client: Client;
  private notionAPI: NotionAPI;

  constructor() {
    this.client = new Client({ auth: NOTION_TOKEN });
    this.notionAPI = new NotionAPI();
  }
  /**
   * 获取指定数据库的条目
   * @param databaseId - 数据库 ID
   * @returns Promise<QueryDatabaseResponse['results']>
   */
  async getDatabase(
    databaseId: string,
  ): Promise<QueryDatabaseResponse['results']> {
    try {
      const response = await this.client.databases.query({
        database_id: databaseId,
      });
      return response.results;
    } catch (error: any) {
      console.error('Error fetching database:', error.body || error);
      throw new Error('Failed to fetch database');
    }
  }
  /**
   * 获取指定页面的内容
   * @param pageId - 页面 ID
   * @returns Promise<PageObjectResponse>
   */
  async getPage(pageId: string) {
    try {
      const page = await this.notionAPI.getPage(pageId);

      return page;
    } catch (error: any) {
      console.error('Error fetching page:', error.body || error);
      throw new Error('Failed to fetch page');
    }
  }
}
export default NotionService;

步骤3:在 Next.js 中使用 Notion API

获取数据库的条目

// pages/api/guide.ts
// 获取旅游指南数据库的条目
import NotionService from '@/lib/notion/NotionServer';
import { NOTION_GUIDE_ID, NOTION_COUNTRY_ID } from '@/lib/constants';
import { formatDatabase } from '@/lib/util';
const notionService = new NotionService();

export const getTravelGuideList = async () => {
  const res = await notionService.getDatabase(NOTION_GUIDE_ID);

  return formatDatabase(res);
};

export const getCountryList = async () => {
  const res = await notionService.getDatabase(NOTION_COUNTRY_ID);

  return formatDatabase(res);
};

在你的页面或组件中使用 getTravelGuideList 函数来获取数据。例如:

// pages/guide/index.tsx
import { useState } from 'react';
import { GetStaticProps } from 'next';
import * as API from '../api/guide';
import GuideCard from '@/components/GuideCard';
import { Post, Country } from '@/lib/type';

interface IndexProps {
  guidesByCountry: Country[];
}

const getGuidesByCountry = (guides: Post[], countries: Country[]) => {
  const res = countries.map((country) => {
    return {
      ...country,
      guides: guides.filter((guide) => country.guide.includes(guide.id)),
    };
  });
  return res;
};

export const getStaticProps: GetStaticProps = async () => {
  const [guideList = [], countryList] = await Promise.all([
    API.getTravelGuideList(),
    API.getCountryList(),
  ]);

  // 提取包含指南的国家列表
  const guidesByCountry = getGuidesByCountry(guideList, countryList);

  return {
    props: {
      guidesByCountry: guidesByCountry,
      posts: guideList,
    },
    revalidate: 10,
  };
};

const Index = ({ guidesByCountry }: IndexProps): React.JSX.Element => {
  const [searchValue, setSearchValue] = useState('');

  return (
    <div className="divide-y divide-gray-200 dark:divide-gray-700  px-4 sm:px-8 lg:px-8">
      {guidesByCountry.map(
        (item: Country) =>
          !!item.guides.length && (
            <div key={item.id} className="w-full pt-8 pb-8">
              <h3 className="mb-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100">
                {item.icon}
                {item.name}
              </h3>
              <div className="grid gap-8 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-4 border-none">
                {item.guides.map((post: Post) => (
                  <GuideCard post={post} key={post.id} />
                ))}
              </div>
            </div>
          ),
      )}
    </div>
  );
};

export default Index;

部署到 Vercel

配置步骤

步骤1: 推送代码到 GitHub

将你的代码推送到 GitHub 仓库:

git init
git remote add origin <https://github.com/yourusername/yourproject.git>
git add .
git commit -m "Initial commit"
git push -u origin main

步骤2:配置 Vercel

  • 登录 Vercel,选择 New Project 并导入你的 GitHub 仓库。
  • 在 Vercel 项目设置中,添加环境变量 NOTION_TOKENNOTION_DATABASE_ID

步骤3:添加自定义域名

  • 在 Vercel 项目设置的 Domains 部分,添加你的自定义域名。
  • 更新你的域名注册商的 DNS 设置,添加 Vercel 提供的 CNAME 和 A 记录。

步骤4:部署

Vercel 将自动构建和部署你的项目。在 DNS 记录传播完成后,你应该可以通过 https://jessieontheroad.com 访问你的博客。

配置域名

在 Vercel 设置自定义域名

  1. 登录到 Vercel选择你的项目

  2. 添加自定义域名

    1. 在项目设置页面中,找到 Domains 部分,然后点击 Add 按钮。
    2. 输入购买的域名 jessieontheroad.com,然后点击 Add
  3. 更新 DNS 记录

    1. Vercel 会提供一组 DNS 记录,你需要将这些记录添加到你的域名注册商(例如 Namecheap、GoDaddy 等)的 DNS 设置中。

    2. 登录到你的域名注册商账户,找到你的域名的 DNS 设置。

    3. 添加 Vercel 提供的 CNAME 和 A 记录。通常会包含以下内容:

      1. CNAME: www -> cname.vercel-dns.com
      2. A 记录: @ -> Vercel 提供的 IP 地址
  4. 等待 DNS 传播

    • DNS 更改可能需要一些时间(通常几分钟到几小时)来传播。你可以使用工具如 DNS Checker 检查你的域名是否正确指向 Vercel。

项目规划

  • 项目设置与环境配置
  • 页面UI
  • 部署域名,部署到 Vercel,配置域名和 SSL 证书
  • 统计和分析
  • 搜索和过滤
  • SEO优化
  • 区分正式、测试环境
  • 订阅和通知
  • tab新增摄影图像
  • 多媒体支持
  • 分类和标签,实现文章的分类和标签功能,提供分类和标签的过滤选项
  • 社交分享,实现文章分享到社交媒体平台的功能
  • 联系表单,配置邮件发送服务