You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中Notion API数据获取与渲染信息处理架构问询

解决方案:分层职责+结构化转换

针对你的场景,最规范简洁的方式是把数据获取、格式转换、UI渲染拆成三层独立职责,既避免在API层生成HTML的耦合,也不让组件处理原始Notion数据的繁琐。

1. 数据获取层:只做数据拉取(/lib/notion.js)

这一层只负责调用Notion SDK获取原始数据,不处理任何格式或结构转换,保持职责单一:

import { Client } from '@notionhq/client';

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

// 获取单个地点+关联城市的原始数据
export async function getPlaceWithCities(placeId) {
  // 拉取地点详情
  const place = await notion.pages.retrieve({ page_id: placeId });
  // 拉取关联的城市数据(假设城市是relation类型属性)
  const cityIds = place.properties.Cities.relation.map(item => item.id);
  const cities = await Promise.all(
    cityIds.map(id => notion.pages.retrieve({ page_id: id }))
  );

  return { place, cities };
}

2. 格式转换层:结构化处理Notion文本(/lib/formatters.js)

专门写工具函数,把Notion的富文本结构转换成组件易读的结构化数据(不是HTML字符串,而是保留样式标记的对象数组):

// 转换单块富文本
function formatTextBlock(block) {
  const { plain_text, annotations, href } = block;
  return {
    content: plain_text,
    styles: {
      bold: annotations.bold,
      italic: annotations.italic,
      strikethrough: annotations.strikethrough,
      underline: annotations.underline,
      code: annotations.code,
      color: annotations.color
    },
    link: href
  };
}

// 转换整个富文本数组
export function formatRichText(richTextArray) {
  return richTextArray.map(formatTextBlock);
}

// 封装地点数据的完整转换
export function formatPlace(rawPlace) {
  return {
    id: rawPlace.id,
    name: rawPlace.properties.Name.title[0].plain_text,
    description: formatRichText(rawPlace.properties.Description.rich_text)
  };
}

// 封装城市数据的完整转换
export function formatCity(rawCity) {
  return {
    id: rawCity.id,
    name: rawCity.properties.Name.title[0].plain_text,
    intro: formatRichText(rawCity.properties.Intro.rich_text)
  };
}

3. 页面与组件层:专注渲染逻辑

页面文件(/pages/[placeId].js)

在getStaticProps里完成数据拉取+格式转换,给组件传递干净的数据:

import { getPlaceWithCities } from '../lib/notion';
import { formatPlace, formatCity } from '../lib/formatters';
import PlaceCard from '../components/PlaceCard';

export async function getStaticProps({ params }) {
  const { place, cities } = await getPlaceWithCities(params.placeId);
  
  return {
    props: {
      place: formatPlace(place),
      cities: cities.map(formatCity)
    },
    revalidate: 86400 // 每天重新同步一次数据
  };
}

export default function PlacePage({ place, cities }) {
  return <PlaceCard place={place} cities={cities} />;
}

通用富文本组件(/components/RichText.js)

专门处理结构化文本的渲染,用JSX映射样式:

export default function RichText({ textBlocks }) {
  return (
    <span>
      {textBlocks.map((block, idx) => {
        let Element = 'span';
        const classNames = [];

        // 根据样式选择元素或添加类名
        if (block.styles.code) Element = 'code';
        if (block.styles.bold) classNames.push('font-bold');
        if (block.styles.italic) classNames.push('italic');
        if (block.styles.strikethrough) classNames.push('line-through');
        if (block.styles.underline) classNames.push('underline');
        if (block.styles.color !== 'default') classNames.push(`text-${block.styles.color}`);

        const props = {
          className: classNames.join(' '),
          ...(block.link && { href: block.link, target: '_blank', rel: 'noopener noreferrer' })
        };

        return <Element key={idx} {...props}>{block.content}</Element>;
      })}
    </span>
  );
}

地点详情组件(/components/PlaceCard.js)

组合其他组件完成最终渲染:

import RichText from './RichText';

export default function PlaceCard({ place, cities }) {
  return (
    <div className="place-card">
      <h1>{place.name}</h1>
      <div className="place-description">
        <RichText textBlocks={place.description} />
      </div>
      <h2>关联城市</h2>
      <div className="cities-list">
        {cities.map(city => (
          <div key={city.id} className="city-item">
            <h3>{city.name}</h3>
            <RichText textBlocks={city.intro} />
          </div>
        ))}
      </div>
    </div>
  );
}

为什么这么做?

  • 职责清晰:每一层只做一件事,新手容易理解和维护,不会把数据逻辑和UI逻辑混在一起。
  • 灵活性高:结构化的文本数据可以在任何组件里复用,还能结合Tailwind、CSS Modules等任意样式方案,比直接生成HTML更灵活。
  • 可扩展性强:以后要加新的文本样式或数据类型,只需要修改转换层或组件层,不用动数据获取逻辑。

内容的提问来源于stack exchange,提问作者linkyndy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 21:23:20