This episode covers API integration in Gatsby: fetching at build time versus runtime, caching strategies for static sites, incremental builds, and how to reduce build time with selective sourcing.

A Gatsby site almost always needs external data — product APIs, external services, or a CMS. The key question isn't "can we", but "when": at build time or at runtime. That choice determines performance and data freshness.
Episode 14 covers fetching at build time and runtime, caching strategies, incremental builds, and selective sourcing to speed up builds.
Data that doesn't change often — like product lists or documentation — is best fetched at build time. The most common pattern is in gatsby-node.js via createPages:
exports.createPages = async ({ actions }) => {
const { createPage } = actions
const response = await fetch("https://api.example.com/products")
const products = await response.json()
products.forEach((product) => {
createPage({
path: `/products/${product.slug}`,
component: require.resolve("./src/templates/product.js"),
context: { product },
})
})
}await fetch(...) runs once per build; the result is rendered into static pages. Visitors never wait on the API — the data is already in the HTML.
For per-user or frequently changing data, fetch on the client with useEffect:
import { useEffect, useState } from "react"
const KursHarian = () => {
const [nilai, setNilai] = useState(null)
useEffect(() => {
fetch("https://api.example.com/kurs")
.then((res) => res.json())
.then(setNilai)
}, [])
return <p>Kurs hari ini: {nilai ?? "memuat..."}</p>
}useEffect ensures the fetch only happens in the browser, not during the build. Combine the two approaches based on the nature of the data: static data at build, dynamic data on the client.
External API calls in gatsby-node.js repeat on every build and can be slow. Gatsby provides a persistent cache API:
exports.sourceNodes = async ({ cache, actions, createContentDigest }) => {
const { createNode } = actions
let data = await cache.get("produk-api")
if (!data) {
const res = await fetch("https://api.example.com/products")
data = await res.json()
await cache.set("produk-api", data)
}
data.forEach((item) => {
createNode({
...item,
id: `Produk-${item.id}`,
internal: {
type: "ProdukApi",
contentDigest: createContentDigest(item),
},
})
})
}cache.get and cache.set store data between builds in the .cache folder. The second build won't call the API again until the cache is cleared — this strategy can cut build time drastically.
For runtime fetches, take advantage of the external API's HTTP caching. Make sure the API responses send the right cache headers so the browser doesn't repeat the request every time.
Incremental builds only build the pages that changed, not the whole site. This is available on Gatsby Cloud and some hosting platforms when using Content Sync. The impact: build time drops from minutes to seconds for large sites.
With incremental builds, a comfortable workflow is: content is changed in the CMS, a webhook triggers a build, and only the affected pages are regenerated. The site stays fresh without sacrificing deploy speed.
Large data sources slow down builds. Selective sourcing means pulling only the data you truly need: set the path folder in gatsby-source-filesystem to only the directories in use, and use filter and limit on CMS queries so unneeded nodes aren't sourced.
Node.js can process several jobs at once via an environment variable:
GATSBY_CPU_COUNT=4 gatsby buildGATSBY_CPU_COUNT controls how many workers Gatsby uses. Adjust it to your CI machine's core count so builds are faster without overloading memory.
Episode 14 completed API integration and caching: distinguishing build-time and runtime fetches, using Gatsby's persistent cache, understanding incremental builds, and selective sourcing for faster builds.
Key takeaways:
gatsby-node.js.useEffect.cache.get and cache.set store API results between builds.GATSBY_CPU_COUNT controls build parallelism.In the next episode, episode 15, we enter the advanced phase: performance optimization — analyzing bundle size and page speed, preloading and code splitting, lazy loading, and improving Core Web Vitals and Lighthouse scores.