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
getStaticPropsorgetServerSideProps. - 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:
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/Ainto 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_titleinstead ofJob Title (public)means someone can retitle the column for readers without breaking your build. -
Filter server-side.
?status=live&limit=50in 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.
- 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
- Authorize requests — Google Sheets API — Google
- Usage limits — Google Sheets API — Google
Related use cases
A No-Code Backend From a Google Sheet
Skip the server. PasteSheet turns a Google Sheet into a live, read-only JSON API your web or mobile app reads from — no database, no backend, no Google setup.
Power a Framer Site With a Google Sheet
Render live data on a Framer site from a Google Sheet. PasteSheet serves cached JSON your code component fetches — no plugin, no backend, no Google Cloud.
Use Google Sheets as a Free Headless CMS
Run your website's content from a Google Sheet. PasteSheet publishes it as a live, read-only JSON API — no database, no backend, no Google Cloud setup.
Drive a Marketing Site from Google Sheets
Run your marketing site's content blocks from a Google Sheet — hero copy, feature lists, section text. PasteSheet publishes it as a live, read-only JSON API.
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.