我得老實說:當客戶在 2023 年第一次要求我使用 Airtable 作為他們的 CMS 時,我以為他們在開玩笑。一個試算表應用程式為生產網站提供動力?但在用這種方式構建了半打網站之後——有些使用 Astro,有些使用 Next.js——我改變了主意。Airtable 對某些項目來說達到了甜蜜點,傳統無頭 CMS 平台完全忽略了這一點。你的行銷團隊已經知道如何使用它。它足夠靈活來建模大多數內容。API 也非常簡單。

但它也有銳利的邊緣。速率限制、附件處理、關係資料古怪的地方——有很多 2023 年的「Airtable 作為 CMS」博客文章從未告訴你的東西。本指南涵蓋了我在 2026 年使用這個堆棧運送真實項目所學到的一切。

目錄

在 2026 年使用 Airtable 作為 Astro 和 Next.js 的 CMS

為什麼 Airtable 作為 CMS 實際上很有意義

Airtable 最大的論點不是技術性的——它是人為的。你的內容編輯已經知道如何使用它。沒有入門摩擦,沒有新登錄要忘記,沒有內容建模 UI 可以學習。他們打開類似試算表的界面,輸入一些東西,它就會出現在網站上。

以下是使其對某些用例確實很好的原因:

  • 編輯者的學習曲線為零。 如果有人可以使用 Google Sheets,他們就可以使用 Airtable。
  • 靈活的模式。 添加新字段只需五秒鐘。無需遷移,無需模式部署。
  • 內置的視圖和篩選器。 編輯可以創建篩選的視圖、看板、圖庫——所有這些都無需開發者幫助。
  • 關係資料。 與平面試算表不同,Airtable 支持連結記錄、查找和匯總。
  • 免費層級足夠慷慨。 免費計畫每個 base 有 1,000 條記錄和 1,000 次 API 調用。Team 計畫(2026 年每個座位每月 $20)將其提升到 50,000 條記錄和更高的 API 限制。

我已經將 Airtable 用作作品集網站、事件清單、團隊目錄、產品目錄、職位委員會和小型博客的 CMS。它對所有這些都出奇地有效。

何時不應該使用 Airtable 作為 CMS

讓我為你省一些痛苦。如果出現以下情況,不要使用 Airtable 作為你的 CMS:

  • 你有超過 ~10,000 條內容記錄。 它會變得緩慢,API 分頁在規模上變成一個真正的頭痛。
  • 你需要帶有嵌入式組件的豐富文本。 Airtable 的長文本字段支持基本 Markdown,但你無法嵌入 React 組件或自定義塊,就像你可以使用 Sanity 或 Contentful 那樣。
  • 你需要對內容進行細粒度的權限。 Airtable 的權限模型是按 base 和按表格,而不是按記錄。如果編輯者 A 不應該看到編輯者 B 的草稿,你會遇到麻煩。
  • 你需要實時預覽。 沒有內置的草稿/預覽工作流程。你可以使用篩選的視圖和狀態字段來破解它,但這很不流暢。
  • 你需要圖像轉換。 Airtable 附件 URL 是臨時的(它們在大約 2 小時後過期)。你需要一個單獨的圖像管道。

對於小到中等內容網站之外的任何內容,你可能最好使用專用的無頭 CMS。我們在我們的無頭 CMS 開發工作中涵蓋了這一點。

為內容設置 Airtable Base

在編寫任何代碼之前,請正確設置你的 Airtable base。以下是我為典型博客使用的結構:

Base 結構

創建一個名為 Posts 的表格,具有以下字段:

字段名稱 字段類型 注釋
Title 單行文本 主字段
Slug 單行文本 URL 安全,小寫
Body 長文本 (Markdown) 啟用豐富文本格式
Excerpt 長文本 純文本,1-2 句
Published 複選框 在生產中過濾此項
Publish Date 日期 按此降序排序
Author 連結到 Authors 表格 關係連結
Tags 多選 或連結到 Tags 表格
Featured Image 附件 單個圖像
SEO Title 單行文本 可選覆蓋
SEO Description 長文本 後設描述

創建一個名為「Published」的篩選視圖,該視圖只顯示 Published 被選中的記錄。這是你的生產內容。

API 設置

  1. 轉到 airtable.com/create/tokens 並創建個人訪問令牌。
  2. 賦予它 data.records:read 範圍(如果你需要寫入訪問,則 data.records:write)。
  3. 將其限定為你使用的特定 base。
  4. 將令牌存儲在你的 .env 文件中。永遠不要提交它。
# .env
AIRTABLE_TOKEN=pat_xxxxxxxxxxxxx
AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX

你可以在 Airtable API 文檔中或查看 base 時的 URL 中找到你的 base ID。

在 2026 年使用 Airtable 作為 Astro 和 Next.js 的 CMS - 架構

將 Airtable 連接到 Astro

Astro 是我對 Airtable 驅動的網站的首選框架,當內容大多是靜態的時。由於 Astro 默認構建為靜態 HTML,你在構建時獲取所有 Airtable 數據,這意味著來自訪問者的零 API 調用以及生產中沒有速率限制問題。

如果你在探索 Astro 用於你的下一個項目,我們對它有深入的經驗——查看我們的 Astro 開發服務

安裝 SDK

npm install airtable

創建數據獲取工具

// src/lib/airtable.ts
import Airtable from 'airtable';

const base = new Airtable({ apiKey: import.meta.env.AIRTABLE_TOKEN })
  .base(import.meta.env.AIRTABLE_BASE_ID);

export interface Post {
  id: string;
  title: string;
  slug: string;
  body: string;
  excerpt: string;
  publishDate: string;
  featuredImage: { url: string; filename: string } | null;
  tags: string[];
}

export async function getPosts(): Promise<Post[]> {
  const records = await base('Posts')
    .select({
      view: 'Published',
      sort: [{ field: 'Publish Date', direction: 'desc' }],
    })
    .all();

  return records.map((record) => ({
    id: record.id,
    title: record.get('Title') as string,
    slug: record.get('Slug') as string,
    body: record.get('Body') as string,
    excerpt: record.get('Excerpt') as string,
    publishDate: record.get('Publish Date') as string,
    featuredImage: record.get('Featured Image')
      ? {
          url: (record.get('Featured Image') as any[])[0].url,
          filename: (record.get('Featured Image') as any[])[0].filename,
        }
      : null,
    tags: (record.get('Tags') as string[]) || [],
  }));
}

export async function getPostBySlug(slug: string): Promise<Post | undefined> {
  const records = await base('Posts')
    .select({
      view: 'Published',
      filterByFormula: `{Slug} = '${slug}'`,
      maxRecords: 1,
    })
    .all();

  if (records.length === 0) return undefined;
  const record = records[0];

  return {
    id: record.id,
    title: record.get('Title') as string,
    slug: record.get('Slug') as string,
    body: record.get('Body') as string,
    excerpt: record.get('Excerpt') as string,
    publishDate: record.get('Publish Date') as string,
    featuredImage: record.get('Featured Image')
      ? {
          url: (record.get('Featured Image') as any[])[0].url,
          filename: (record.get('Featured Image') as any[])[0].filename,
        }
      : null,
    tags: (record.get('Tags') as string[]) || [],
  };
}

在 Astro 頁面中使用它

---
// src/pages/blog/[slug].astro
import { getPosts, getPostBySlug } from '../../lib/airtable';
import Layout from '../../layouts/Layout.astro';

export async function getStaticPaths() {
  const posts = await getPosts();
  return posts.map((post) => ({
    params: { slug: post.slug },
  }));
}

const { slug } = Astro.params;
const post = await getPostBySlug(slug!);

if (!post) return Astro.redirect('/404');
---

<Layout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    <time>{post.publishDate}</time>
    <div set:html={post.body} />
  </article>
</Layout>

就是這樣。在 astro build 時,每個文章都從 Airtable 獲取並呈現為靜態 HTML。你的生產網站進行零 API 調用。

將 Airtable 連接到 Next.js

Next.js 提供更多靈活性。你可以使用 generateStaticParams 在構建時獲取,使用伺服器組件在請求時獲取,或使用 ISR(增量靜態再生成)以獲得兩者的最佳結合。

我們構建了很多 Next.js 網站——這是我們的麵包和黃油。請查看我們的 Next.js 開發能力

獲取工具(Next.js 版本)

我更喜歡在 Next.js 中直接使用 Airtable REST API 和 fetch,而不是 SDK。它使你能更好地控制使用 Next.js 的 fetch 擴展進行快取。

// lib/airtable.ts
const AIRTABLE_TOKEN = process.env.AIRTABLE_TOKEN!;
const AIRTABLE_BASE_ID = process.env.AIRTABLE_BASE_ID!;

const headers = {
  Authorization: `Bearer ${AIRTABLE_TOKEN}`,
  'Content-Type': 'application/json',
};

export async function fetchPosts() {
  const url = new URL(
    `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/Posts`
  );
  url.searchParams.set('view', 'Published');
  url.searchParams.set('sort[0][field]', 'Publish Date');
  url.searchParams.set('sort[0][direction]', 'desc');

  const res = await fetch(url.toString(), {
    headers,
    next: { revalidate: 60 }, // ISR: 每 60 秒重新驗證一次
  });

  if (!res.ok) throw new Error(`Airtable API error: ${res.status}`);

  const data = await res.json();
  return data.records.map((record: any) => ({
    id: record.id,
    title: record.fields['Title'],
    slug: record.fields['Slug'],
    body: record.fields['Body'],
    excerpt: record.fields['Excerpt'],
    publishDate: record.fields['Publish Date'],
    tags: record.fields['Tags'] || [],
  }));
}

使用 App Router 的 ISR 頁面

// app/blog/[slug]/page.tsx
import { fetchPosts } from '@/lib/airtable';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const posts = await fetchPosts();
  return posts.map((post: any) => ({ slug: post.slug }));
}

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const posts = await fetchPosts();
  const post = posts.find((p: any) => p.slug === slug);

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{post.publishDate}</time>
      <div dangerouslySetInnerHTML={{ __html: post.body }} />
    </article>
  );
}

使用 revalidate: 60,Next.js 將提供快取的頁面並在背景中最多每 60 秒刷新一次。你的編輯更新 Airtable,網站在一分鐘內更新。無需設置 webhook,無需觸發重建。

處理圖像和附件

這是使用 Airtable 作為 CMS 的單個最大陷阱。Airtable 附件 URL 過期。 它們是簽名 URL,在大約 2 小時後變為無效。如果你直接在 HTML 中呈現它們,它們將中斷。

以下是你的選項:

選項 1:在構建時下載 (Astro)

對於靜態網站,在構建期間下載圖像並在本地提供它們:

import fs from 'fs/promises';
import path from 'path';

async function downloadImage(url: string, filename: string) {
  const res = await fetch(url);
  const buffer = Buffer.from(await res.arrayBuffer());
  const outputPath = path.join('public', 'images', 'cms', filename);
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
  await fs.writeFile(outputPath, buffer);
  return `/images/cms/${filename}`;
}

選項 2:通過 CDN 代理

設置 Cloudflare Worker 或 Vercel Edge 函數來代理 Airtable 圖像 URL,快取它們,並通過你自己的域提供。這適用於 Astro 和 Next.js。

選項 3:使用單獨的圖像主機

將圖像上載到 Cloudinary、Imgix 或 S3 bucket,並在文本字段中存儲永久 URL,而不是使用 Airtable 的附件字段。這是我對生產網站的建議——它是最可靠的方法。

快取、速率限制和性能

Airtable 的 API 有嚴格的速率限制:每個 base 每秒 5 個請求。 這不多。以下是如何保持在此之下。

策略 框架 工作原理
靜態生成 Astro 所有 API 調用在構建時發生。零執行時調用。
ISR Next.js 快取的響應,按計時器重新驗證。
內存快取 兩者 使用 TTL 在 Map 中快取 API 響應。
Webhook + 重建 兩者 Airtable 自動化觸發 Vercel/Netlify 重建。
Redis/KV 快取 Next.js (Vercel) 將 API 響應存儲在 Vercel KV 或 Upstash Redis 中。

對於 Astro 網站,構建時方法意味著你只在部署期間點擊 API。對於使用 ISR 的 Next.js,你最多每個重新驗證間隔每頁點擊一次。

如果你有很多頁面和短的重新驗證間隔,考慮一次獲取所有記錄並快取整個數據集,而不是進行每頁 API 調用。

分頁很重要

Airtable 每個請求最多返回 100 條記錄。SDK 中的 .all() 方法自動處理分頁,但如果你直接使用 fetch,你需要遵循 offset 令牌:

async function fetchAllRecords(tableName: string) {
  let allRecords: any[] = [];
  let offset: string | undefined;

  do {
    const url = new URL(
      `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${tableName}`
    );
    url.searchParams.set('view', 'Published');
    if (offset) url.searchParams.set('offset', offset);

    const res = await fetch(url.toString(), { headers });
    const data = await res.json();

    allRecords = [...allRecords, ...data.records];
    offset = data.offset;
  } while (offset);

  return allRecords;
}

豐富文本和 Markdown 內容

Airtable 的長文本字段可以存儲 Markdown(如果你啟用「豐富文本」選項)。但你從 API 返回的是 Markdown 格式的文本,而不是 HTML。

你需要轉換它。我在簡單情況下使用 marked,或對更多控制使用 unifiedremark 插件:

import { marked } from 'marked';

const htmlContent = marked.parse(post.body);

對於 Astro,你也可以使用內置的 Markdown 處理:

---
import { marked } from 'marked';
const html = marked.parse(post.body);
---
<article set:html={html} />

要注意的一件事:Airtable 的豐富文本編輯器生成自己的 Markdown 風格。它對粗體、斜體、連結、標題和清單處理得很好。代碼塊和表格受支持但可能很棘手。如果你的內容需要複雜的格式,考慮讓編輯者以純 Markdown 模式編寫。

Airtable 與傳統無頭 CMS 選項

讓我們對 2026 年的權衡進行實際評估。以下是 Airtable 與專用無頭 CMS 平台的比較:

特性 Airtable Sanity Contentful Strapi
編輯者學習曲線 非常低 中等 中等
內容建模 靈活,非正式 優秀 優秀
API 速率限制 每個 base 5 req/s 慷慨 (CDN) 慷慨 (CDN) 自託管
圖像處理 過期 URL 內置 CDN 內置 CDN 自託管
預覽/草稿 手動(複選框) 內置 內置 內置
定價(5 人團隊) $100/月 (Team) 免費層級可行 $300/月+ 免費(自託管)
Webhook 支持 通過自動化 內置 內置 內置
豐富文本質量 基本 Markdown 便攜文本 結構化 豐富文本
關係內容 連結記錄 參考 參考 關係

Airtable 在編輯者體驗和靈活性上勝出。它在圖像處理、預覽工作流程和 API 可靠性上輸掉規模。對於編輯者已經在 Airtable 中的小到中等網站?這是一個堅實的選擇。對於內容豐富且工作流程複雜的網站?選擇一個真正的 CMS。

真實世界架構模式

以下是我在生產中使用的模式:

模式 1:使用 Astro + 重建 Webhook 的完全靜態

最適合:行銷網站、作品集、記錄少於 500 條的目錄。

  1. Astro 在構建時獲取所有 Airtable 數據。
  2. Airtable 自動化在記錄更新時向 Vercel/Netlify 發送 webhook。
  3. 網站在 30-60 秒內重建。
  4. 圖像在構建時下載——沒有過期 URL 問題。

模式 2:使用 Next.js 的 ISR

最適合:博客、目錄、經常更新的網站。

  1. Next.js 使用 ISR 生成頁面(每 60-300 秒重新驗證一次)。
  2. Airtable API 每次重新驗證時每個唯一頁面調用一次。
  3. 圖像通過 Cloudinary 代理或下載到 CDN。
  4. 編輯者在沒有觸發完整重建的情況下在幾分鐘內看到更新。

模式 3:Airtable + 補充 CMS

最適合:某些內容存在於 Airtable 中,其他內容需要更豐富編輯的網站。

  1. 結構化資料(團隊成員、事件、產品)保留在 Airtable 中。
  2. 長篇內容(博客文章、案例研究)進入 Sanity 或 Notion。
  3. 前端在構建時或使用 ISR 時從兩個源獲取。

這種混合方法比你想的要常見。我們已經用這種方式構建了幾個網站——如果你在考慮類似的東西,讓我們談論一下

從 Airtable 觸發重建

Airtable 具有內置自動化,可以觸發 webhook。在 Posts 表格中的「當記錄更新時」上設置觸發器,然後向你的部署平台的構建 hook 發送 POST 請求:

// Vercel 部署 hook
https://api.vercel.com/v1/integrations/deploy/prj_xxxx/yyyy

// Netlify 構建 hook
https://api.netlify.com/build_hooks/xxxxxxxxxxxx

在自動化中添加 30 秒延遲以批量快速編輯。

常見問題

我可以免費使用 Airtable 作為 CMS 嗎? Airtable 的免費計畫包括每個 base 1,000 條記錄和每月 1,000 次 API 調用。這對小網站足夠,但對於任何重要的東西,你可能需要 Team 計畫(2026 年每個座位每月 $20)。Team 計畫為你提供 50,000 條記錄和更高的 API 限制。

我如何處理 Airtable 的過期圖像 URL? Airtable 附件 URL 在大約 2 小時後過期。對於使用 Astro 構建的靜態網站,在構建時下載圖像。對於使用 ISR 的 Next.js,要么通過 Cloudinary 之類的 CDN 代理圖像,要么將圖像 URL 存儲在單獨的圖像託管服務中,並在 Airtable 中將其引用為文本字段。

Airtable 可以處理有數百個文章的博客嗎? 是的,在某個點之前。Airtable 可以很好地處理數百條記錄。一旦進入數千條,API 分頁和構建時間開始變得明顯。對於少於 1,000 篇文章的博客,它工作得很好。超過這個,考慮專用的無頭 CMS。

Airtable 比 Notion 作為 CMS 更好嗎? 它們解決不同的問題。Airtable 對於結構化資料(產品、事件、團隊成員)更好,因為它的關係資料庫模型。Notion 對長篇書面內容更好,因為它的基於塊的編輯器。Airtable 的 API 也比 Notion 的更成熟和快速。

我如何使用 Airtable 設置預覽/草稿功能? 添加一個「Status」單選字段,選項包括「Draft」、「In Review」和「Published」。為每個狀態創建一個篩選視圖。你的生產網站從「Published」視圖獲取。對於預覽,創建一個單獨的預覽路由,從「In Review」視圖獲取,受身份驗證保護。

我應該使用 Airtable SDK 還是直接使用 REST API? 對於 Astro,官方 airtable npm 套件工作得很好,因為你在構建時獲取。對於 Next.js,我建議直接使用 REST API 的 fetch——它使你能控制 Next.js 快取指令,如 revalidatetags。SDK 不理解 Next.js 的擴展 fetch 選項。

Airtable 允許的最大 API 調用數是多少? Airtable 對每個 base 強制執行 5 個請求/秒的速率限制。超過此返回 429 狀態代碼。在 Team 計畫上,你獲得更高的每月通話額度,但每秒速率限制保持不變。靜態生成和 ISR 是最小化 API 使用的最佳方法。

我可以在同一個項目中同時使用 Airtable 和 Astro 及 Next.js 嗎? 不完全在同一個項目中,但你可以有一個共享的 Airtable base 為多個前端提供動力。某些團隊使用 Astro 用於其行銷網站,Next.js 用於其 web 應用,兩者都從同一 Airtable base 讀取。只需留意所有消費者之間的共享速率限制。