PasteSheet icon PasteSheet logo mark — a spreadsheet grid with a curly brace on a green rounded square PasteSheet

Read a Google Sheet from a Next.js Server Component

A Google Sheet is a fast way to give a Next.js app editable content without a database. PasteSheet serves it as typed JSON you fetch in a Server Component, getStaticProps, or ISR — cached at the edge, read-only, and set up by pasting a share URL instead of wiring OAuth.

Last updated

Key facts

  • Google's API requires a Google Cloud project plus OAuth credentials or a service account before it will return a single row. source
  • Google's Sheets API is cell-oriented: it reads ranges like A1:D50, not records. Reassembling those into row objects is work you do yourself. source
  • A cached endpoint reads the sheet once per TTL, not once per request — so 10,000 visitors become one upstream read, and Google's quota stops being your problem.
  • Match your ISR window to the cache TTL: Free is a fixed 5 minutes (revalidate: 300), while paid plans set a custom TTL from 30 seconds to 1 hour.

A spreadsheet as your content layer

Not every Next.js app needs a CMS or a Postgres box behind it. For marketing pages, a catalog, docs, or a directory, the content is a table your team would rather edit in a spreadsheet than a database admin. The friction is getting that sheet into your app as clean, typed data — Google's own API means a Cloud project, OAuth, and cell-range parsing.

PasteSheet removes that. You fetch() a normal JSON endpoint from a Server Component or a build-time data function, and the rows come back typed and keyed by column. Because it is a server fetch, your keys never touch the browser, and Next.js caching keeps it fast.

Where it slots in

The same endpoint fits every Next.js data-fetching path:

  • App Router: await fetch() in an async Server Component.
  • ISR: pass next: { revalidate } to rebuild the page on a schedule.
  • Pages Router: call it from getStaticProps or getServerSideProps.
  • Client-side: fetch a public endpoint directly for a live dashboard or search box.

Fetch in a Server Component with ISR

Type the row shape, fetch the endpoint with a revalidate window, and render — no client JavaScript required:

app/products/page.tsx
type Product = { slug: string; name: string; price: number }

export default async function Products() {
  const res = await fetch(
    "https://pastesheet.com/your-endpoint/Products?sort=name",
    { next: { revalidate: 300 } } // ISR: rebuild every 5 minutes
  )
  const { data }: { data: Product[] } = await res.json()

  return (
    <ul>
      {data.map((p) => (
        <li key={p.slug}>{p.name} — ${p.price}</li>
      ))}
    </ul>
  )
}

Getting the caching layers to agree

There are two caches in this stack and they are easy to confuse. The endpoint TTL controls how often PasteSheet re-reads Google. The ISR revalidate window controls how often Next.js rebuilds the page. Worst case, an edit waits for both — so a 5-minute TTL behind a 5-minute revalidate can take up to ten minutes to appear.

The fix is to make one of them clearly the shorter. Set the endpoint TTL below your revalidate window and the ISR window becomes the only number you have to reason about. For content that must be near-instant, drop to revalidate: 0 (or cache: 'no-store') and let the endpoint's own cache do the protecting — you still are not hitting Google on every request.

Practical notes for a real app

  • Type the response, do not trust it. Column inference is good, but a colleague can type N/A into a number column. Validate at the boundary with Zod if the render would break on a bad value.
  • Use the alias, not the header. Querying job_title instead of Job Title (public) means someone can retitle the column for readers without breaking your build.
  • Filter server-side. ?status=live&limit=50 in the URL beats fetching everything and filtering in JS — less payload, less work in the render.
  • Fail soft on build. Wrap the fetch so a transient error falls back to the last good data rather than failing the whole static build.
  • Keep the key server-side. A private endpoint's bearer token belongs in an env var read by a Server Component — never in a client component or a NEXT_PUBLIC_ variable.
  • Set the row cap deliberately. A sheet that grows past your plan's cap truncates silently in a paginated UI unless you page through it.

When a sheet is the wrong data source

This pattern is a good fit for read-heavy, human-edited, tabular content — catalogs, docs indexes, directories, pricing. It is a poor fit the moment you need writes from the app, per-user data, relational joins, or transactional guarantees. A sheet has no constraints, no migrations, and no concurrency story beyond "last edit wins".

The honest test is whether a non-technical person editing a cell is a feature. If yes, a sheet is often better than a database. If the thought makes you nervous, you want a real backend.

Read-only, no Google Cloud project

Because the endpoint is read-only by design, it is safe to expose publicly or hand to a teammate: consumers can read and query the data but can never change the sheet. And there is no Google Cloud project, OAuth screen, or service account to set up — you paste a share URL and get a live API.

What it costs

Free

For side projects and trying things out.

$0 /mo
Endpoints
3
Requests / mo
2,000
Row cap
500
  • Type-mapped columns
  • Private endpoints & keys
  • Custom cache TTL

Start free. The Free plan runs 3 live endpoints with a 5-minute cache — enough for a small site or prototype. Upgrade for private endpoints and keys, a custom cache TTL that pairs with ISR, and full-text search.

Frequently asked questions

Does this work with the App Router and Pages Router?

Both. Await the endpoint in an async Server Component (App Router), or call it from getStaticProps/getServerSideProps (Pages Router). It is a normal fetch, so any React setup can use it.

How do I revalidate the data?

Use Next.js ISR: pass next: { revalidate: seconds } to fetch so the page rebuilds on a schedule. Match it to your PasteSheet cache TTL so a sheet edit flows through predictably.

Are columns typed?

Yes. PasteSheet infers each column's type, so numbers and dates come back as real values rather than strings — you can annotate the row shape and render directly.

How do I keep the data out of public view?

The sheet has to stay link-shared for PasteSheet to read it, but the endpoint can be private: send an Authorization: Bearer key from your server fetch. Because the request runs server-side, the key is never exposed to the browser.

Why does my sheet edit take so long to appear?

Two caches are stacked. The endpoint TTL controls how often PasteSheet re-reads Google, and the ISR revalidate window controls how often Next.js rebuilds. In the worst case an edit waits for both, so set the endpoint TTL below your revalidate window and reason about one number.

Can I use a Google Sheet instead of a CMS in Next.js?

For read-heavy tabular content — catalogs, directories, pricing, docs indexes — yes, and the editing experience is usually better. It is the wrong choice when you need writes from the app, per-user data, relational joins, or transactional guarantees.

Does this work with React Server Components and streaming?

Yes. It is a plain fetch inside an async Server Component, so it composes with Suspense boundaries and streaming exactly like any other data source.

What happens if the fetch fails during a build?

Next.js fails the static build by default. Wrap the call and fall back to the last known-good data or an empty list so a transient upstream error cannot break a deploy.

Will TypeScript types be generated for me?

Not automatically in your project, but the free Google Sheets to TypeScript tool generates an interface from your sheet's columns that you can paste in and keep alongside the fetch.

Sources

Related use cases

Turn your sheet into an API in minutes

Paste a Google Sheet URL and get a live REST API and MCP server — no backend, no code, free to start.