Publish Airtable Rows with Next.js

Deploy a Next.js app that renders live Airtable rows on a Webflow site.

Introduction

30 Second Summary

Teams keep their freshest content in spreadsheets. Public pages often fall behind because each edit needs a separate update.

In this project, you will deploy a Next.js app that turns Airtable rows into a live page mounted through Webflow Cloud. Reloading the page reveals each Airtable edit while the token stays on the server.

What You'll Build

Picture editing a row in Airtable, refreshing your mounted Webflow path, and watching the live page change without another deployment.

By the end of this project, you'll have:

  • A live Airtable-powered page that displays the latest table rows whenever someone reloads your mounted Webflow path.
  • A working JSON API route that returns the same current records for other apps or services to use.
  • A server-side data flow that keeps the scoped read-only token out of the page source visitors receive.
  • Secret Mission: Add a live record counter that changes whenever rows are added to or removed from Airtable.

Are there any prerequisites?

You'll need Node, creator access to an Airtable base, and a Webflow site. The free Starter site plan supports Webflow Cloud on a webflow.io path.

Before We Start

Publishing live rows requires access to the services behind the project.

Your Airtable account holds the data your app reads. Your Webflow account gives that app a live home.

Your computer also needs Node.js, npm, a terminal, and a code editor before the local build begins.

In this step, get ready to:
  • Confirm access to your Airtable account.
  • Confirm access to your Webflow account.
  • Prepare Node.js, npm, a terminal, and a code editor.
Confirm your account access

Account setup can feel like the point where secret keys appear. No project token is created here.

  • Create an Airtable account from the official sign-up page if you do not already have one.
  • Sign in to Airtable from its home page.
  • Confirm your signed-in Airtable account home loads.
  • Create a Webflow account from the official sign-up page if you do not already have one.
  • Sign in to Webflow from its home page.
  • Confirm your signed-in Webflow account home loads.

That is the online setup sorted. Both services are ready for the project work.

Prepare your terminal and editor

The terminal runs project commands. Visual Studio Code gives you a place to edit the app files.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter.

You should see a terminal window with a command prompt ready for input.

  • Install Visual Studio Code from its official download page if you do not already have a code editor.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to return to your search bar.
  • Type Visual Studio Code and press Enter.
  • Confirm the editor window opens.

Your terminal and editor are now available for the local build.

Editor Not Opening?

  • Return to the official download page.
  • Download the installer that matches your operating system.
  • Run the installer again.

If the editor still does not open, ask for help with the installation issue.

Verify Node.js and npm

Node.js runs the Next.js tooling on your computer. npm comes with Node.js and installs the packages your app needs.

  • Return to the terminal window from earlier.

Before you check, ask yourself whether both tools will print a version number.

  • Check both tools by running these commands:
node --version
npm --version

What Do These Commands Check?

  • The node --version command asks Node.js to print its installed version.
  • The npm --version command asks npm to print its installed version.

✔️ I see two version numbers

That completes your local setup. Both tools are available from the terminal.

ⓧ One or both commands are missing

  • Open the official Node.js download page.
  • Download the LTS installer for your operating system.
  • Run the installer with its default options.
  • Return to the terminal from earlier.
  • Confirm the installation by running these commands:
node --version
npm --version

What Confirms the Installation?

Each command prints a version number when its tool is available from the terminal.

Still Missing a Command?

  • Close the terminal window.
  • Open a new Terminal or PowerShell window through your operating system search.
  • Repeat the version check in the new window.

If a command remains unavailable, ask for help with your Node.js installation.

Your accounts and local tools are ready. Next, you will add the first Airtable rows that your app can publish.

Create Airtable Content and Token

Your live page stays useful when its source content is easy to update. Airtable gives your team that editing surface.

This step creates the table that the app reads. You also create a personal access token with read-only access to that table.

In this step, get ready to:
  • Create a base with a table named Content.
  • Fill the table with publishable rows.
  • Create a read-only token for the base.
Create the Content table

Each row becomes one item on the live page. Consistent field names give the app a predictable structure to read.

  • Open the Airtable homepage in your browser.
  • Sign in with your Airtable account.
  • Click + Create on the Airtable homepage.
  • Select the workspace where you want to keep this project.
  • Choose Build an app on your own.
  • Name the base Live Site Content.

You should see the new Live Site Content base with its first table ready for editing.

  • Click the arrow next to the first table's name.
  • Click Rename table.
  • Enter Content as the table name.
  • Click the dropdown arrow next to the first field header.
  • Click Edit field.
  • Enter Name as the field name.
  • Click Save.

The table tab now says Content. The first field header now says Name.

  • Click the + icon to the right of the last field header.
  • Select Long text as the field type.
  • Enter Description as the field name.
  • Click Create field.

You should now see Name beside Description in the table header.

  • Enter Launch Notes in the first row's Name cell.
  • Enter Read the latest product updates. in its Description cell.
  • Enter Team Story in the second row's Name cell.
  • Enter Meet the people behind the work. in its Description cell.
  • Enter Contact Details in the third row's Name cell.
  • Enter Find the best way to reach us. in its Description cell.

You should see three filled rows under the Name and Description fields. These rows are ready to become page content.

Create the read-only token

The token authenticates the app when it requests records from Airtable. Its scope controls what the app can do.

Why use one narrow scope?

The data.records:read scope allows the token to list records or retrieve a record. It does not grant record-writing access.

Limiting the token to Live Site Content prevents it from reading your other bases.

The scope list should contain only data.records:read.

  • Click + Add a base.
  • Select Live Site Content from your bases.
  • Review the scope list to confirm that it contains only data.records:read.
  • Review the resource list to confirm that it contains only Live Site Content.

The next screen shows the secret only once. Your scope settings remain visible later without revealing the token value.

  • Click Create token.
  • Copy the token from the one-time reveal.
  • Store the token in a password manager or another private location.
  • Dismiss the one-time token view.

Good work. The credential can read your published content without changing it.

Can't create the token?

  • Confirm that your Airtable account can open Live Site Content.
  • Return to the token editor.
  • Check that data.records:read is the only selected scope.

Help me create my read-only Airtable token. You can also compare account-specific guidance in the Airtable Community.

Verify the table and token

The final check confirms that your content has a stable structure. It also proves that the credential follows least privilege.

  • Switch back to the Live Site Content base from earlier.
  • Select the Content table.

You should see Name beside Description above three filled rows.

Before you review the token details, which single scope and base do you expect to see?

  • Return to the Personal access tokens panel.
  • Select Webflow live content.

You should see data.records:read as the only scope. You should see Live Site Content as the only base resource.

The token value is no longer displayed.

  • Capture a screenshot showing the scope list and base resource.
  • Keep the token value outside the screenshot.

That's the sensitive setup handled: your team can edit the table while the token stays private. Next, you'll scaffold the app and print these records locally.

Build the Airtable Fetch Helper

Your Airtable table now has publishable rows. Your scoped token gives the app permission to read them.

Airtable can return records across multiple pages. This step builds the server-side helper that follows every pagination offset before printing the complete result locally.

In this step, get ready to:
  • Prepare a supported Node.js runtime.
  • Scaffold the local Next.js application.
  • Fetch every Airtable page through a server-side helper.
Check Node.js and scaffold the app

The app uses Next.js version 15 or higher. The local helper check relies on native TypeScript support from Node.js version 22.18.0 or higher.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows).
  • Press Enter to open the terminal.
  • Check your installed Node.js version by running:
node --version

What does this command check?

The command prints the Node.js version available in this terminal. Version 22.18.0 or higher can run the TypeScript helper check used later in this step.

✔️ I see version 22.18.0 or higher

Your Node.js runtime is ready for the Next.js scaffold and the local Airtable check.

ⓧ I see an older version

Your current Node.js version needs an upgrade before it can run this project.

  • Visit the official Node.js download page.
  • Download the current LTS installer for your operating system.
  • Run the downloaded installer.
  • Return to the terminal from earlier.
  • Repeat the version check above.

ⓧ Command not found

Your terminal cannot find Node.js yet. Install the current LTS release before continuing.

  • Visit the official Node.js download page.
  • Download the current LTS installer for your operating system.
  • Run the downloaded installer.
  • Close the terminal after installation.
  • Open a new terminal through your operating system search.
  • Repeat the version check above.
  • Move to your Desktop by running this command:
cd ~/Desktop

Why start on your Desktop?

This sets a predictable starting location for the project. You can find the new airtable-live-site folder on your Desktop after the scaffold finishes.

The scaffold may take a minute while it downloads the framework and installs its dependencies. Terminal output during that wait means the setup is still moving.

  • Create the airtable-live-site application by running these commands:
npx create-next-app@latest airtable-live-site --typescript --eslint --app --use-npm --yes
cd airtable-live-site

What does this scaffold create?

  • The --typescript option creates a typed project for the Airtable response shapes.
  • The --eslint option adds code-quality checks.
  • The --app option creates an App Router project.
  • The --use-npm option installs dependencies with npm.
  • The --yes option accepts the documented defaults without an interactive wizard.
  • Confirm the scaffolded files exist by running:
ls

What does this check prove?

The command lists the files inside airtable-live-site. A successful scaffold includes the app directory and package.json.

You should see the app directory. You should also see package.json and package-lock.json.

Before you start the development server, what page do you expect a fresh Next.js scaffold to show?

  • Start the local development server by running:
npm run dev

What does this command start?

The development server runs your scaffolded app locally. It keeps control of this terminal until you stop it.

  • Open http://localhost:3000 in your browser.

You should see the default Next.js page. That first local page proves the scaffold and its installed dependencies are working.

  • Return to the terminal from earlier.
  • Stop the development server by pressing Ctrl+C.

Scaffold or page not working?

Confirm the terminal path ends with airtable-live-site before starting the server. A different path means npm cannot find this app's scripts.

If the scaffold command fails during download, check your internet connection before running it again.

Help me troubleshoot my Next.js scaffold.

Configure the server-only environment variables

The helper needs three values at runtime. A local environment file keeps the Airtable token outside the code that reaches the browser.

The base ID is easy to miss inside the Airtable URL. It is the path segment that begins with app.

  • Switch back to the Airtable base from earlier.
  • Copy the URL path segment beginning with app here: your Airtable base ID.
  • Use your code editor's Open Folder command to open Desktop/airtable-live-site.
  • Create .env.local inside the airtable-live-site folder.
  • Paste this environment configuration into .env.local:
# Keep the personal access token on the server.
AIRTABLE_TOKEN=your-airtable-token-here
# Identify the base and Content table to read.
AIRTABLE_BASE_ID=[[AIRTABLE_BASE_ID="your Airtable base ID"]]
AIRTABLE_TABLE_ID=Content

What do these variables control?

  • AIRTABLE_TOKEN authenticates the server-side request with your scoped personal access token.
  • AIRTABLE_BASE_ID selects the Airtable base that contains your rows.
  • AIRTABLE_TABLE_ID points the request at the Content table.

This next edit handles a live credential. Keep the token inside .env.local so it never appears in a client component or screenshot.

  • Replace your-airtable-token-here with the complete token value from earlier.
  • Save .env.local.
  • Confirm the hidden file exists by running:
ls -a

Why include hidden files?

Files beginning with a dot are hidden by default on many systems. The -a option includes .env.local in the directory listing.

You should see .env.local in the output. The command prints only its filename, so your token stays hidden.

  • Open .gitignore from your code editor's file panel.
  • Confirm the file contains .env*.
  • Add .env* on a new line if it is missing.

That ignore rule keeps the local environment file out of source control. The token also stays server-only because its name has no NEXT_PUBLIC_ prefix.

Environment file not showing?

Confirm the filename starts with a dot and ends with .local. A filename such as .env.local.txt will not load as the intended environment file.

Keep the environment file directly inside airtable-live-site beside package.json.

Help me check my local environment setup.

Fetch every Airtable page

The Airtable API returns one page of records at a time. A response includes an offset when another page is available.

Why follow the offset?

Each Airtable page contains up to 100 records. The helper sends the returned offset with the next request until Airtable stops returning one.

This loop prevents a larger table from being silently cut off after its first page.

  • Create a lib folder inside airtable-live-site using your code editor's file panel.
  • Create airtable.ts inside the new lib folder.
  • Confirm the helper file exists by running:
ls lib

What does this check confirm?

The command lists the contents of the new lib folder. You should see airtable.ts in the output.

  • Add the Airtable response types to lib/airtable.ts by pasting:
// Describe each record returned by Airtable.
export type AirtableRecord = {
  id: string;
  createdTime: string;
  fields: Record<string, unknown>;
};

// Describe one paginated API response.
type AirtableResponse = {
  records: AirtableRecord[];
  offset?: string;
};

What do these types describe?

  • AirtableRecord describes the record ID, creation time, and table fields returned by Airtable.
  • AirtableResponse describes one page of records plus its optional pagination offset.
  • Record<string, unknown> allows the helper to support the fields chosen for your live page.
  • Save lib/airtable.ts.
  • Check the response types by running:
npx tsc --noEmit

What does this type check do?

The TypeScript compiler checks the file without creating JavaScript output. A successful check returns you to the terminal prompt without reporting a type error.

Seeing a type error?

Compare each property name and punctuation mark with the response types above. A missing brace can make the error appear several lines below the actual typo.

Help me fix the Airtable response types.

  • Add the fetch helper below the response types in lib/airtable.ts by pasting:
export async function getRecords(): Promise<AirtableRecord[]> {
  // Read the server-only Airtable configuration.
  const { AIRTABLE_TOKEN: token, AIRTABLE_BASE_ID: baseId, AIRTABLE_TABLE_ID: tableId } = process.env;

  if (!token || !baseId || !tableId) throw new Error('AIRTABLE_TOKEN, AIRTABLE_BASE_ID and AIRTABLE_TABLE_ID must be set');

  // Collect every page into one array.
  const records: AirtableRecord[] = [];
  let offset: string | undefined;

  do {
    // Build the next page URL from the returned offset.
    const url = new URL(`https://api.airtable.com/v0/${baseId}/${tableId}`);
    if (offset) url.searchParams.set('offset', offset);

    // Authenticate without exposing the token to the browser.
    const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });

    if (!res.ok) {
      throw new Error(`Airtable responded with ${res.status}`);
    }

    const data = (await res.json()) as AirtableResponse;
    records.push(...data.records);
    offset = data.offset;
  } while (offset);

  return records;
}

What does this helper do?

  • getRecords() reads the three server-side environment variables when the helper runs.
  • URL builds the Airtable request from your base and table values.
  • Authorization sends the personal access token in the request header.
  • records.push(...data.records) combines each returned page into one array.
  • do...while continues until the API response has no further offset.
  • Save lib/airtable.ts.

Before you run the helper, how many Airtable records do you expect it to print?

  • Load .env.local and print the helper result by running:
node --env-file=.env.local --eval "import('./lib/airtable.ts').then(async ({ getRecords }) => console.log(JSON.stringify(await getRecords(), null, 2)))"

What does this local check do?

The --env-file=.env.local option loads your local Airtable configuration into process.env.

The evaluated script imports getRecords() and prints its returned array as formatted JSON. It never prints the token.

You should see a JSON array containing the rows from your Content table. Each item should include an id and a fields object with values such as Name or Description.

That is the bridge working. Your local server-side code can now collect the complete Airtable table without exposing its credential.

Records not printing?

If the helper reports missing environment variables, confirm .env.local uses the exact names shown earlier. Keep the file beside package.json.

If Airtable rejects the request, confirm the base ID belongs to the base granted to your token. Confirm the table value is exactly Content.

Help me debug my Airtable fetch helper.

✔️ Awesome, I've got everything!

Great. Confirm lib/airtable.ts is saved before moving on.

ⓧ I'd like to double check the full code

Here is the complete lib/airtable.ts file for comparison:

// Describe each record returned by Airtable.
export type AirtableRecord = {
  id: string;
  createdTime: string;
  fields: Record<string, unknown>;
};

// Describe one paginated API response.
type AirtableResponse = {
  records: AirtableRecord[];
  offset?: string;
};

export async function getRecords(): Promise<AirtableRecord[]> {
  // Read the server-only Airtable configuration.
  const { AIRTABLE_TOKEN: token, AIRTABLE_BASE_ID: baseId, AIRTABLE_TABLE_ID: tableId } = process.env;

  if (!token || !baseId || !tableId) throw new Error('AIRTABLE_TOKEN, AIRTABLE_BASE_ID and AIRTABLE_TABLE_ID must be set');

  // Collect every page into one array.
  const records: AirtableRecord[] = [];
  let offset: string | undefined;

  do {
    // Build the next page URL from the returned offset.
    const url = new URL(`https://api.airtable.com/v0/${baseId}/${tableId}`);
    if (offset) url.searchParams.set('offset', offset);

    // Authenticate without exposing the token to the browser.
    const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });

    if (!res.ok) {
      throw new Error(`Airtable responded with ${res.status}`);
    }

    const data = (await res.json()) as AirtableResponse;
    records.push(...data.records);
    offset = data.offset;
  } while (offset);

  return records;
}

Your app can now retrieve every Airtable row through one server-side function. Next up, you will expose those records through a JSON API route.

Expose Records Through an API Route

Your paginated helper can already collect every row from Airtable. The rest of your app still needs a stable way to request those records.

This step adds a Next.js Route Handler at /api/records. The handler becomes a server-side API boundary that returns JSON while keeping your environment variables out of the response.

In this step, get ready to:
  • Create a Route Handler that calls the existing Airtable helper.
  • Return the Content table records as JSON.
  • Verify the local endpoint with curl without exposing the Airtable token.
Create the records route

A Route Handler connects an HTTP method to server code. Your GET() function can call getRecords() without sending the Airtable token to the browser.

  • Create app/api/records/route.ts with the file controls in the editor from earlier.

You should now see route.ts nested inside the app/api/records folders in your file tree.

  • Add the server-side records handler by pasting this code into app/api/records/route.ts:
import { NextResponse } from 'next/server';
import { getRecords } from '@/lib/airtable';

// Load Airtable data on the server for this API endpoint
export async function GET() {
  try {
    const records = await getRecords();

    return NextResponse.json({ records });
  } catch {
    // Keep internal errors out of the public response
    return NextResponse.json(
      { error: 'Unable to load records' },
      { status: 502 },
    );
  }
}

What does this code do?

  • The GET() function handles requests sent to /api/records.
  • The getRecords() call uses your existing pagination helper to retrieve the complete Content table.
  • NextResponse.json({ records }) serializes the returned records into a JSON response.
  • The catch block returns a generic 502 response without exposing internal error details.
  • Save app/api/records/route.ts.
  • Start the local development server in the terminal panel from earlier by running:
npm run dev

What does this command do?

This starts the Next.js development server for your airtable-live-site app. The server loads your new Route Handler whenever a request reaches /api/records.

You should see the development server report that the app is ready at http://localhost:3000.

Development Server Not Starting?

Confirm that your terminal is inside the airtable-live-site folder. A terminal opened from another folder cannot find this app's package configuration.

If another development process is using the local port, stop that process from its original terminal before trying again.

Help me troubleshoot my Next.js development server.

✔️ Awesome, I've got everything!

Your records route is saved. Keep the development server running for the request test.

ⓧ I'd like to double check the full code

Compare your complete app/api/records/route.ts file with this version:

import { NextResponse } from 'next/server';
import { getRecords } from '@/lib/airtable';

// Load Airtable data on the server for this API endpoint
export async function GET() {
  try {
    const records = await getRecords();

    return NextResponse.json({ records });
  } catch {
    // Keep internal errors out of the public response
    return NextResponse.json(
      { error: 'Unable to load records' },
      { status: 502 },
    );
  }
}
Request the records with curl

The development server keeps your first terminal occupied. A second terminal lets you send a request while the app continues running.

  • Open a second terminal panel in your editor.

Before you send the request, do you expect the response to contain records or server configuration?

  • Request the local records endpoint by running:
curl http://localhost:3000/api/records

What does this command do?

The curl command sends a request to your local /api/records endpoint. It prints the Route Handler's JSON response directly in the terminal.

You should see a JSON object containing a records array. Each item should contain fields from your Content table.

The response contains Airtable record data only. Your token stays in the server-side local environment file.

Not Seeing Your Records?

If the request cannot connect, confirm that the development server is still running in your first terminal.

If the route returns an error response, check the server terminal for the underlying problem. Keep every Airtable credential private while troubleshooting.

Help me debug my local records endpoint.

You now have a working server-side API boundary that returns your Airtable records as JSON. Next, you'll turn those records into a page that visitors can read.

Render Live Airtable Records

Your API route already proves that Airtable records can reach your Next.js server as JSON. Visitors still need a page that turns those records into something they can read.

The page will call the same paginated helper during server-side rendering. Explicit dynamic rendering makes each request check Airtable again.

In this step, get ready to:
  • Replace the starter page with a list of records from the Content table.
  • Enable dynamic rendering for every request.
  • Verify that an Airtable edit appears after a page reload.
Render the records on the page

A Server Component can await the existing helper before it returns HTML. The Airtable token stays inside server-side code.

  • Return to the code editor containing your airtable-live-site app.
  • Select app/page.tsx in the editor's file sidebar.
  • Replace the contents of app/page.tsx by pasting this code:
import { getAirtableRecords } from "@/lib/airtable";

// Read Airtable again for every request
export const dynamic = 'force-dynamic'

export default async function Home() {
  // Load every paginated record on the server
  const records = await getAirtableRecords();

  return (
    <main>
      <h1>Live Airtable Content</h1>
      <ul>
        {records.map((record) => (
          <li key={record.id}>
            <h2>{String(record.fields.Name ?? "Untitled")}</h2>
            <p>{String(record.fields.Description ?? "")}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

What does this page do?

  • The dynamic setting uses force-dynamic so Next.js renders the route at request time.
  • The getAirtableRecords() helper loads the table from server-side code. Its existing pagination collects every page of records.
  • The record.id value gives each list item a stable key.
  • The Name field becomes each record's heading. The Description field becomes its supporting text.
  • Save app/page.tsx.
  • Return to the local page in your browser.

Before you reload, predict how the records from your Content table will be arranged on the page.

  • Reload the local page.

You should see the heading Live Airtable Content. Each Airtable row should appear with its Name above its Description.

Page not showing your records?

  • Confirm that the development server is running in the terminal from earlier.
  • Compare the helper import in app/page.tsx with the working import in your API route.
  • Check that every reference to Name matches the Airtable field's capitalization.

Help me debug why my Next.js page is not rendering the records returned by my Airtable helper.

✔️ Awesome, I've got everything!

Your records page is saved. The local page should now show every row returned by your paginated helper.

ⓧ I'd like to double check the full code

Compare your complete app/page.tsx file with this version:

import { getAirtableRecords } from "@/lib/airtable";

// Read Airtable again for every request
export const dynamic = 'force-dynamic'

export default async function Home() {
  // Load every paginated record on the server
  const records = await getAirtableRecords();

  return (
    <main>
      <h1>Live Airtable Content</h1>
      <ul>
        {records.map((record) => (
          <li key={record.id}>
            <h2>{String(record.fields.Name ?? "Untitled")}</h2>
            <p>{String(record.fields.Description ?? "")}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}
Prove the page stays live

The first render proves that the page can read Airtable. Editing a row tests whether a later request receives fresh data.

  • Switch back to the Content table in Airtable.
  • Select a row that is visible on your local page.
  • Replace that row's Name value with Live update confirmed.
  • Click outside the edited cell.
  • Return to the local page in your browser.

Before you reload, picture which heading should change when the page requests Airtable again.

  • Reload the local page.

You should see Live update confirmed in the list. The change appears without restarting the development server.

That is the live loop working. Your local page picked up the Airtable edit without a rebuild.

Still seeing the old value?

  • Switch back to Airtable to confirm that Live update confirmed remains in the edited cell.
  • Confirm that export const dynamic = 'force-dynamic' appears near the top of app/page.tsx.
  • Reload the local page again after the Airtable edit finishes saving.

Help me find why my dynamically rendered Next.js page still shows an older Airtable value.

Your local site now turns Airtable rows into a page that refreshes from the source on every request. Next, you will deploy that same live page to its mounted path on Webflow Cloud.

Deploy to Webflow Cloud

Your Airtable edits already appear in the local Next.js page without a rebuild. That proves the live-data loop works on your machine.

Webflow Cloud turns that local result into a page on your Webflow site.

You will mount the app at /live-airtable. Deployment environment variables keep Airtable access on the server.

In this step, get ready to:
  • Prepare the Webflow CLI for deployment.
  • Deploy airtable-live-site to the /live-airtable mount path.
  • Add the server-only Airtable variables to Webflow Cloud.
  • Verify the live page plus its JSON API route.
Prepare the Webflow CLI

The Webflow CLI packages your existing app for Webflow Cloud. Its authentication flow connects the terminal to your Webflow workspace.

  • Return to the terminal for your airtable-live-site app.
  • Install the Webflow CLI globally and confirm it is available by running:
npm install -g @webflow/webflow-cli
webflow --version

What do these commands do?

  • The first command installs the Webflow CLI through npm.
  • The -g flag makes the webflow command available throughout your computer.
  • The second command prints the installed CLI version.

You should see a version number in the terminal. The Webflow CLI is ready to accept deployment commands.

Command not available?

Close your terminal after the installation completes. Return to your project in a new terminal so it reloads the command path.

If the installation reports a permissions problem, use a terminal account that can install global npm packages.

Help me troubleshoot my Webflow CLI installation.

Authentication opens a browser window for Webflow approval. The terminal waits while you select your workspace.

  • Authenticate the CLI and check the resulting session by running:
webflow auth login
webflow auth status

How does authentication work?

The login command opens Webflow's authorization flow in your browser. The status command confirms which Webflow account the CLI can use.

This session lets the deployment command create an app inside the workspace you approve.

  • Select the workspace containing your Webflow site.
  • Approve the authorization request.
  • Return to your terminal after the browser confirms authorization.

You should see your authenticated Webflow account in the status output.

Authentication not completing?

Check the browser for an unfinished workspace selection. Complete that approval before returning to the terminal.

Help me complete Webflow CLI authentication.

Create the mounted deployment

A mount path places the app beneath your existing Webflow site address. Webflow Cloud applies /live-airtable when it builds the app.

The first run presents an Existing site target followed by a site picker. The build can take up to two minutes after those selections.

  • Start the first site-attached deployment by running:
webflow cloud deploy --app-name airtable-live-site --environment production --mount /live-airtable --auto-publish

What does this deployment command do?

  • The --app-name option names the Webflow Cloud app airtable-live-site.
  • The --environment option targets an environment named production.
  • The --mount option assigns the app to /live-airtable.
  • The --auto-publish option publishes the site after the deployment succeeds.
  • Select Existing site when the deploy target prompt appears.
  • Select the Webflow site where you want the app mounted.
  • Wait for the deployment status to report success.

You should see a successful deployment associated with the production environment. Its mount path should show /live-airtable.

Build not succeeding?

Open the failed deployment's build logs in Webflow Cloud. Confirm that Webflow detected the app as Next.js.

If the logs point to the API route runtime setting, remove that runtime export from the route. Redeploy after saving the change.

Help me diagnose my Webflow Cloud build logs.

Add deployment variables and verify the app

Webflow Cloud cannot read the local environment file on your computer. Its environment needs the same three Airtable keys before the deployed page can retrieve records.

  • Open your Webflow site's settings from the Webflow dashboard.
  • Select Webflow Cloud.
  • Select the airtable-live-site app.
  • Select the production environment.
  • Select the Environment Variables tab.

Keep the token masked

Your Airtable token is a credential. Mark its Webflow Cloud variable as Secret so its value stays masked in the dashboard plus deployment logs.

  • Click Add variable.
  • Select Add single variable.
  • Enter the Airtable token variable name from your local environment file in Key.
  • Enter its corresponding token value in Value.
  • Enable Secret.
  • Click Add variable to save it.

Good progress. Your Airtable token is now stored as a masked deployment secret.

  • Click Add variable.
  • Select Add single variable.
  • Enter the Airtable base ID variable name from your local environment file in Key.
  • Enter its corresponding base ID in Value.
  • Click Add variable to save it.

The base ID variable should now appear beside the masked token variable.

  • Click Add variable.
  • Select Add single variable.
  • Enter the Airtable table name variable from your local environment file in Key.
  • Enter Content in Value.
  • Click Add variable to save it.

The environment now lists all three server-side Airtable variables. The token value remains masked.

Why redeploy after adding variables?

Webflow Cloud applies the current environment configuration to a new deployment. Redeploying gives the running app access to the three Airtable values.

This second build can take up to two minutes. A short wait here means Webflow Cloud is rebuilding the mounted app.

Pause for a prediction: will both the page and API route find the same Airtable records.

  • Redeploy the app with its environment variables by running:
webflow cloud deploy --app-name airtable-live-site --environment production --mount /live-airtable --auto-publish

What happens during this redeploy?

Webflow Cloud rebuilds the same app for the same environment. The new deployment receives the Airtable variables you added.

The mount remains /live-airtable because the command supplies the same path again.

You should see the latest deployment reach a successful status.

  • Record the Environment URL shown in Webflow Cloud here: your Webflow live URL ending in /live-airtable.
  • Visit your Webflow live URL ending in /live-airtable in a browser tab.

You should see the current records from the Airtable Content table. The edited row should match the value you saw locally.

  • Open the mounted version of the API route by adding the same API route path you tested earlier to the live URL.

You should see the Airtable records returned as JSON. This confirms the deployed page plus its server-side API can both reach Airtable.

Live records not loading?

Return to the Environment Variables tab. Compare each key with the corresponding key in your local environment file.

Confirm the table value is Content. Check that the latest deployment completed after you added the variables.

Help me troubleshoot my deployed Airtable page.

That is the deployment complete. Your Webflow site now serves the same live Airtable list that worked locally.

Your app is live at its mounted Webflow path. Next up, you will inspect the response and confirm that the Airtable token never reaches visitors.

Verify the Token Stays Private

Your Airtable records are now live through Webflow Cloud at /live-airtable. The deployment proves that visitors can see current content.

The final risk sits at the browser boundary. You will inspect the page source plus the HTTP response to prove your server-only environment variable never exposes the personal access token.

In this step, get ready to:
  • Search the live page source for the Airtable token.
  • Document the dynamic-rendering compatibility note.
  • Inspect the live response for the same token.
Inspect the live page source

Server-side rendering sends generated HTML to the browser. Your rendered records belong in that HTML. The Airtable credential must remain on the server.

Protect the token during the check

Handling the token directly can feel risky. This search stays inside your browser.

  • Keep screen sharing off during the search.
  • Close the browser's Find field before taking any screenshot.
  • Return to the mounted page at /live-airtable.
  • Press Cmd+Option+U (macOS) or Ctrl+U (Windows) to view the page source.
  • Copy only the Airtable token value from the local environment configuration you used earlier.

Before you search, pause to predict whether the server-rendered HTML contains the token.

  • Press Cmd+F (macOS) or Ctrl+F (Windows) to open the Find field.
  • Paste the token into the Find field.

You should see that the browser found no matches. This confirms that the token was not serialized into the HTML sent to visitors.

  • Delete the token from the Find field.
  • Close the Find field.
  • Copy /live-airtable to replace the token in your clipboard.

What does this source check prove?

Page source shows the HTML that the server returned to your browser. Finding the records without finding the token proves that the page can publish Airtable content without publishing its credential.

Found the token in the source?

A match means visitor-facing code received the credential. Treat that result as a security issue.

  • Stop before sharing any screenshots.
  • Remove the token from every object returned by the page or API route.
  • Redeploy the app before repeating the source check.
  • Revoke the exposed token in Airtable before creating a replacement.

Help me investigate why my Airtable token appears in the deployed page source.

Record the build compatibility note

Dynamic rendering tells Next.js to retrieve fresh data for each request. The explicit configuration depends on support from the deployment target.

Your current deployment works with this configuration. Keeping a compatibility note gives you a clear place to start if a future deployment fails.

  • Record in your notes for airtable-live-site that the explicit dynamic-rendering configuration is framework-recommended.
  • Add that the same configuration can break builds in incompatible deployment configurations.

Your note now preserves why the configuration exists. It also identifies the compatibility check to make if another deployment target rejects the build.

Inspect the live response

The Network response shows the payload your browser received for the live request. Inspecting that payload closes the remaining gap in the privacy check.

  • Return to the live page at /live-airtable.
  • Right-click an empty area of the page.
  • Select Inspect to open Chrome DevTools.
  • Select the Network panel.
  • Reload the live page.
  • Select the document request containing /live-airtable.
  • Select the Response tab.

Before you search, predict whether the live response contains current records, the token, or both.

  • Press Cmd+F (macOS) or Ctrl+F (Windows) inside the Response tab.
  • Paste the Airtable token into the Find field.

You should see that the browser found no matches. You should still see content from the current Airtable records in the response.

  • Delete the token from the Find field.
  • Close the Find field.
  • Copy /live-airtable to replace the token in your clipboard.

Cannot find the live response?

  • Keep the Network panel open.
  • Reload the live page to capture a fresh request.
  • Select the document request whose path contains /live-airtable.
  • Open its Response tab.

Help me locate the deployed page response in Chrome DevTools.

That closes the final security check. Your visitors receive current Airtable records while the token stays on the server.

Secret mission

Audit the Live Data Pipeline

A production data pipeline needs proof that every public view reflects its source. Trace one Airtable record through the JSON API and live page. Then run a reversible edit that tests the complete deployed path.

Clean Up Your Resources

Clean Up Your Resources

Choose how much of the project you want to keep. The Webflow Cloud deployment stays at $0 on the webflow.io path used here.

Resources you used:

  • An Airtable base containing the Content table with your project rows.
  • A read-only personal access token scoped to the Airtable base.
  • The Webflow Cloud deployment airtable-live-site mounted at /live-airtable.
  • Server-only Webflow Cloud environment variables for the Airtable connection.
  • A local airtable-live-site Next.js app with its local environment configuration.

Keep everything running

No action needed. Choose this if you want the live page to keep reflecting edits from the Content table.

  • Keep airtable-live-site deployed at /live-airtable.
  • Leave the read-only Airtable token active while the app needs access.
  • Keep the Content table available for future edits.
  • Keep the local airtable-live-site folder in its saved location.

Pause - I'll come back to this later

Pausing here means revoking the current Airtable token. That credential cannot be restored, but your app and table stay intact.

  • Revoke the read-only token from Airtable's personal access token management page.
  • Reload the mounted /live-airtable path.

You should no longer see current Airtable records load because every request now uses a revoked credential.

When you return, a fresh read-only token reconnects the same app.

  • Create a new personal access token with read-only record access to the same Airtable base.
  • Replace the revoked token value in the server-only Webflow Cloud environment configuration.
  • Replace the revoked token value in the local environment configuration.
  • Reload /live-airtable to test the restored connection.

You should see the current Content rows on the live page again.

Delete - I don't want to use this again

Remove all project resources if you want to start fresh. This cleanup targets only the resources named in this project.

Deleting the Content table is permanent, so use this path only when you no longer need its rows.

  • Revoke the read-only token from Airtable's personal access token management page.
  • Delete the Content table from its table menu in the Airtable base from earlier.
  • Delete airtable-live-site with its server-only environment configuration from the app settings in Webflow Cloud.
  • Delete the local airtable-live-site folder from the location where you saved it.
  • Visit the mounted /live-airtable path on your Webflow site.
  • Return to the Airtable base to confirm the project table is gone.

You should no longer see the live Airtable list at /live-airtable.

The Content table should be absent from the base. The local airtable-live-site folder should be absent from its saved location.

Nice Work!

Nice Work!

Excellent work! Your Next.js app now publishes current Airtable rows through Webflow Cloud while keeping the credential on the server.

You've learned how to:

  • Built a server-side Airtable fetch helper that follows pagination. Every row in the Content table can reach the app.
  • Created a JSON API route plus a dynamically rendered page. Airtable edits appear after a reload without a redeploy.
  • Deployed the app to Webflow Cloud at /live-airtable. You verified that the Airtable token stays private by inspecting the live source plus the response.
  • Secret Mission: completed an optional challenge to push your live data publishing skills further.

Ready to quiz yourself?