Seré honesto: cuando un cliente me pidió por primera vez que usara Airtable como su CMS en 2023, pensé que estaba bromeando. ¿Una aplicación de hojas de cálculo alimentando un sitio web en producción? Pero después de construir media docena de sitios de esta manera — algunos con Astro, otros con Next.js — cambié de opinión. Airtable alcanza un punto dulce para ciertos proyectos que las plataformas CMS headless tradicionales simplemente pierden. Tu equipo de marketing ya sabe cómo usarlo. Es lo suficientemente flexible para modelar la mayoría del contenido. Y la API es muy simple.

Pero no está exenta de aristas afiladas. Límites de velocidad, manejo de adjuntos, peculiaridades de datos relacionales — hay mucho que los posts de blog "Airtable como CMS" de 2023 nunca te dijeron. Esta guía cubre todo lo que he aprendido enviando proyectos reales con este stack en 2026.

Tabla de contenidos

Usar Airtable como CMS con Astro y Next.js en 2026

Por qué Airtable como CMS realmente tiene sentido

El mayor argumento a favor de Airtable no es técnico — es humano. Tus editores de contenido ya saben cómo usarlo. No hay fricción de incorporación, sin nuevo inicio de sesión que olvidar, sin interfaz de modelado de contenido que aprender. Abren una interfaz similar a una hoja de cálculo, escriben contenido, y aparece en el sitio web.

Aquí está lo que lo hace genuinamente bueno para ciertos casos de uso:

  • Curva de aprendizaje cero para editores. Si alguien puede usar Google Sheets, puede usar Airtable.
  • Schema flexible. Agregar un nuevo campo toma cinco segundos. Sin migraciones, sin despliegues de schema.
  • Vistas y filtros incorporados. Los editores pueden crear vistas filtradas, tableros Kanban, galerías — todo sin ayuda del desarrollador.
  • Datos relacionales. A diferencia de las hojas de cálculo planas, Airtable admite registros vinculados, búsquedas y resúmenes.
  • El nivel gratuito es generoso. 1,000 registros por base y 1,000 llamadas API por mes en el plan gratuito. El plan Team ($20/usuario/mes en 2026) te da 50,000 registros y límites de API más altos.

He usado Airtable como CMS para sitios de portafolio, listados de eventos, directorios de equipo, catálogos de productos, tableros de trabajos y blogs pequeños. Funciona sorprendentemente bien para todos estos.

Cuándo NO deberías usar Airtable como CMS

Déjame ahorrarte algo de dolor. No uses Airtable como tu CMS si:

  • Tienes más de ~10,000 registros de contenido. Se vuelve lento, y la paginación de API se convierte en un verdadero dolor de cabeza a escala.
  • Necesitas texto enriquecido con componentes integrados. Los campos de texto largo de Airtable admiten Markdown básico, pero no puedes incrustar componentes React o bloques personalizados como puedes con Sanity o Contentful.
  • Necesitas permisos granulares en el contenido. El modelo de permisos de Airtable es por base y por tabla, no por registro. Si el editor A no debería ver los borradores del editor B, vas a tener un mal momento.
  • Necesitas vista previa en tiempo real. No hay flujo de trabajo de borrador/vista previa incorporado. Puedes hackearlo con vistas filtradas y un campo de estado, pero es desordenado.
  • Necesitas transformaciones de imágenes. Las URLs de adjuntos de Airtable son temporales (expiran después de aproximadamente 2 horas). Necesitarás un pipeline de imágenes separado.

Para cualquier cosa más allá de un sitio de contenido pequeño a mediano, probablemente estés mejor con un CMS headless de propósito específico. Cubrimos eso en nuestro trabajo de desarrollo de CMS headless.

Configurar tu base de Airtable para contenido

Antes de escribir cualquier código, prepara bien tu base de Airtable. Aquí está la estructura que uso para un blog típico:

Estructura de base

Crea una tabla llamada Posts con estos campos:

Nombre del campo Tipo de campo Notas
Title Texto de una sola línea Campo principal
Slug Texto de una sola línea Seguro para URL, minúsculas
Body Texto largo (Markdown) Habilitar formato de texto enriquecido
Excerpt Texto largo Texto plano, 1-2 oraciones
Published Casilla de verificación Filtrar esto para producción
Publish Date Fecha Ordenar por esto descendente
Author Enlace a tabla Authors Enlace relacional
Tags Selección múltiple O enlace a tabla Tags
Featured Image Adjunto Imagen única
SEO Title Texto de una sola línea Override opcional
SEO Description Texto largo Meta descripción

Crea una vista filtrada llamada "Published" que solo muestre registros donde Published está marcado. Este es tu contenido de producción.

Configuración de API

  1. Ve a airtable.com/create/tokens y crea un token de acceso personal.
  2. Dale alcance data.records:read (y data.records:write si necesitas acceso de escritura).
  3. Limítalo a la base específica que estás usando.
  4. Almacena el token en tu archivo .env. Nunca lo confirmes.
# .env
AIRTABLE_TOKEN=pat_xxxxxxxxxxxxx
AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX

Puedes encontrar tu ID de base en la documentación de API de Airtable o en la URL al ver tu base.

Usar Airtable como CMS con Astro y Next.js en 2026 - arquitectura

Conectar Airtable a Astro

Astro es mi marco preferido para sitios impulsados por Airtable cuando el contenido es en su mayoría estático. Dado que Astro se construye en HTML estático por defecto, obtienes todos tus datos de Airtable en tiempo de compilación, lo que significa cero llamadas API de tus visitantes y sin preocupaciones por límites de velocidad en producción.

Si estás explorando Astro para tu próximo proyecto, tenemos experiencia profunda con él — revisa nuestros servicios de desarrollo Astro.

Instalar el SDK

npm install airtable

Crear una utilidad de obtención de datos

// 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[]) || [],
  };
}

Usarlo en páginas 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>

Eso es todo. En astro build, cada post se obtiene de Airtable y se renderiza en HTML estático. Tu sitio en producción realiza cero llamadas API.

Conectar Airtable a Next.js

Next.js te da más flexibilidad. Puedes obtener en tiempo de compilación con generateStaticParams, en tiempo de solicitud con componentes del servidor, o usar ISR (Regeneración Estática Incremental) para lo mejor de ambos mundos.

Construimos muchos sitios Next.js — es nuestro pan y mantequilla. Ve nuestras capacidades de desarrollo Next.js.

La utilidad de obtención (versión Next.js)

Prefiero usar la API REST de Airtable directamente con fetch en Next.js en lugar del SDK. Te da mejor control sobre el caché con las extensiones de fetch de Next.js.

// 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: revalidar cada 60 segundos
  });

  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'] || [],
  }));
}

Página ISR con App Router

// 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>
  );
}

Con revalidate: 60, Next.js servirá la página en caché y la actualizará en el trasfondo como máximo una vez cada 60 segundos. Tus editores actualizan Airtable, y el sitio se actualiza en un minuto. Sin configuración de webhook, sin disparadores de reconstrucción.

Manejo de imágenes y adjuntos

Este es el mayor gotcha con Airtable como CMS. Las URLs de adjuntos de Airtable expiran. Son URLs firmadas que se vuelven inválidas después de aproximadamente 2 horas. Si las renderizas directamente en tu HTML, se romperán.

Aquí están tus opciones:

Opción 1: Descargar en tiempo de compilación (Astro)

Para sitios estáticos, descarga imágenes durante la compilación y sírvelas localmente:

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}`;
}

Opción 2: Proxy a través de un CDN

Configura un Cloudflare Worker o función de Vercel Edge que haga proxy de URLs de imágenes de Airtable, las cachea, y las sirve a través de tu propio dominio. Funciona para Astro y Next.js.

Opción 3: Usar un host de imágenes separado

Sube imágenes a Cloudinary, Imgix, o un bucket S3, y almacena la URL permanente en un campo de texto en lugar de usar el campo de adjuntos de Airtable. Esto es lo que recomiendo para sitios en producción — es el enfoque más confiable.

Caché, límites de velocidad y rendimiento

La API de Airtable tiene límites de velocidad estrictos: 5 solicitudes por segundo por base. Eso no es mucho. Aquí está cómo mantenerse muy por debajo.

Estrategia Marco Cómo funciona
Generación estática Astro Todas las llamadas API ocurren en tiempo de compilación. Cero llamadas en tiempo de ejecución.
ISR Next.js Respuestas en caché, revalidadas en un temporizador.
Caché en memoria Ambos Cachea respuestas de API en un Map con TTL.
Webhook + reconstrucción Ambos La automatización de Airtable desencadena una reconstrucción de Vercel/Netlify.
Caché Redis/KV Next.js (Vercel) Almacena respuestas de API en Vercel KV o Upstash Redis.

Para sitios Astro, el enfoque en tiempo de compilación significa que solo accedes a la API durante despliegues. Para Next.js con ISR, accederás como máximo una vez por intervalo de revalidación por página.

Si tienes muchas páginas e intervalos de revalidación cortos, considera obtener todos los registros a la vez y cachear todo el conjunto de datos en lugar de hacer llamadas API por página.

La paginación importa

Airtable devuelve un máximo de 100 registros por solicitud. El método .all() en el SDK maneja la paginación automáticamente, pero si estás usando fetch directamente, necesitas seguir el token 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;
}

Contenido de texto enriquecido y Markdown

Los campos de texto largo de Airtable pueden almacenar Markdown si habilitas la opción "rich text". Pero lo que obtienes de la API es texto formateado en Markdown, no HTML.

Necesitas convertirlo. Uso marked para casos simples o unified con plugins remark para más control:

import { marked } from 'marked';

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

Para Astro, también puedes usar el procesamiento incorporado de Markdown:

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

Una cosa a tener en cuenta: el editor de texto enriquecido de Airtable produce su propio sabor de Markdown. Maneja bien negritas, itálicas, enlaces, encabezados y listas. Los bloques de código y las tablas son compatibles pero pueden ser complicados. Si tu contenido necesita formato complejo, considera que los editores escriban en modo Markdown plano.

Airtable vs opciones tradicionales de CMS headless

Seamos reales sobre las compensaciones. Aquí está cómo se compara Airtable con plataformas CMS headless de propósito específico en 2026:

Característica Airtable Sanity Contentful Strapi
Curva de aprendizaje del editor Muy baja Media Media Buena
Modelado de contenido Flexible, informal Excelente Excelente Bueno
Límites de velocidad de API 5 req/s por base Generoso (CDN) Generoso (CDN) Auto-hospedado
Manejo de imágenes URLs vencidas CDN incorporado CDN incorporado Auto-hospedado
Vista previa/borradores Manual (casilla) Incorporado Incorporado Incorporado
Precio (equipo de 5) $100/mes (Team) Nivel gratuito viable $300/mes+ Gratuito (auto-hospedado)
Soporte de webhook Via automatizaciones Incorporado Incorporado Incorporado
Calidad de texto enriquecido Markdown básico Portable Text Estructurado Texto enriquecido
Contenido relacional Registros vinculados Referencias Referencias Relaciones

Airtable gana en experiencia del editor y flexibilidad. Pierde en manejo de imágenes, flujos de trabajo de vista previa y confiabilidad de API a escala. ¿Para sitios pequeños a medianos donde tus editores ya están en Airtable? Es una opción sólida. ¿Para sitios ricos en contenido con flujos de trabajo complejos? Ve con un CMS real.

Patrones de arquitectura del mundo real

Aquí están los patrones que he usado en producción:

Patrón 1: Completamente estático con Astro + Webhooks de reconstrucción

Mejor para: sitios de marketing, portafolios, directorios con < 500 registros.

  1. Astro obtiene todos los datos de Airtable en tiempo de compilación.
  2. La automatización de Airtable envía un webhook a Vercel/Netlify en la actualización de registro.
  3. El sitio se reconstruye en 30-60 segundos.
  4. Las imágenes se descargan en tiempo de compilación — sin problemas de URL vencidas.

Patrón 2: ISR con Next.js

Mejor para: blogs, catálogos, sitios con actualizaciones frecuentes.

  1. Next.js genera páginas con ISR (revalidar cada 60-300 segundos).
  2. API de Airtable llamada como máximo una vez por revalidación por página única.
  3. Las imágenes se hacen proxy a través de Cloudinary o se descargan en un CDN.
  4. Los editores ven actualizaciones en minutos sin disparar una reconstrucción completa.

Patrón 3: Airtable + CMS complementario

Mejor para: sitios donde algún contenido vive en Airtable y otro contenido necesita edición más rica.

  1. Los datos estructurados (miembros del equipo, eventos, productos) se quedan en Airtable.
  2. El contenido de larga extensión (publicaciones de blog, estudios de caso) va a Sanity o Notion.
  3. El frontend obtiene de ambas fuentes en tiempo de compilación o con ISR.

Este enfoque híbrido es más común de lo que crees. Hemos construido varios sitios de esta manera — si estás considerando algo similar, hablemos sobre ello.

Desencadenar reconstrucciones desde Airtable

Airtable tiene automatizaciones incorporadas que pueden disparar webhooks. Configura un disparador en "When a record is updated" en tu tabla Posts, luego envía una solicitud POST al gancho de compilación de tu plataforma de despliegue:

// Gancho de despliegue de Vercel
https://api.vercel.com/v1/integrations/deploy/prj_xxxx/yyyy

// Gancho de compilación de Netlify
https://api.netlify.com/build_hooks/xxxxxxxxxxxx

Añade un retraso de 30 segundos en la automatización para agrupar ediciones rápidas.

Preguntas frecuentes

¿Es Airtable gratis para usar como CMS? El plan gratuito de Airtable incluye 1,000 registros por base y 1,000 llamadas API por mes. Eso es suficiente para un sitio pequeño, pero probablemente necesitarás el plan Team ($20/usuario/mes en 2026) para cualquier cosa seria. El plan Team te da 50,000 registros y límites de API más altos.

¿Cómo manejo las URLs de imágenes vencidas de Airtable? Las URLs de adjuntos de Airtable expiran después de aproximadamente 2 horas. Para sitios estáticos construidos con Astro, descarga imágenes en tiempo de compilación. Para Next.js con ISR, haz proxy de imágenes a través de un CDN como Cloudinary, o almacena URLs de imágenes en un servicio de alojamiento de imágenes separado y referencíalas como campos de texto en Airtable.

¿Puede Airtable manejar un blog con cientos de publicaciones? Sí, hasta cierto punto. Airtable maneja cientos de registros bien. Una vez que llegas a los miles, la paginación de API y los tiempos de compilación comienzan a ser notables. Para un blog con menos de 1,000 publicaciones, funciona bien. Más allá de eso, considera un CMS headless dedicado.

¿Es Airtable mejor que Notion como CMS? Resolven diferentes problemas. Airtable es mejor para datos estructurados (productos, eventos, miembros del equipo) por su modelo de base de datos relacional. Notion es mejor para contenido escrito largo porque tiene un editor basado en bloques. La API de Airtable también es más madura y rápida que la de Notion.

¿Cómo configuro la funcionalidad de vista previa/borrador con Airtable? Añade un campo "Status" de selección única con opciones como "Draft", "In Review" y "Published". Crea una vista filtrada para cada estado. Tu sitio en producción obtiene de la vista "Published". Para vistas previas, crea una ruta de vista previa separada que obtenga de la vista "In Review", protegida por autenticación.

¿Debo usar el SDK de Airtable o la API REST directamente? Para Astro, el paquete npm oficial airtable funciona bien ya que estás obteniendo en tiempo de compilación. Para Next.js, recomiendo usar fetch directamente con la API REST — te da control sobre directivas de caché de Next.js como revalidate y tags. El SDK no entiende las opciones extendidas de fetch de Next.js.

¿Cuál es el número máximo de llamadas API que permite Airtable? Airtable impone un límite de velocidad de 5 solicitudes por segundo por base. Exceder esto devuelve un código de estado 429. En el plan Team, obtienes una asignación de llamadas mensuales más alta, pero el límite de velocidad por segundo se mantiene igual. La generación estática e ISR son las mejores formas de minimizar el uso de API.

¿Puedo usar Airtable con Astro y Next.js en el mismo proyecto? No exactamente en el mismo proyecto, pero puedes tener una base de Airtable compartida impulsando múltiples frontends. Algunos equipos usan Astro para su sitio de marketing y Next.js para su aplicación web, ambos leyendo de la misma base de Airtable. Solo sé consciente de los límites de velocidad compartidos en todos los consumidores.