Snapshot mode
Snapshot mode lets the Content Island API client serve all of its read methods from a local file instead of calling the API on every request. A project’s full content is exported once as a single JSON document — the snapshot (transferred gzip-compressed from the export endpoint, then stored as plain JSON on disk) — and the client reads from it with drop-in parity to the live API: the same methods, the same shapes, the same results.
Snapshot mode is designed for applications that need to read content frequently without making an API request on every operation.
Export the project once, keep the snapshot locally, and resolve all read operations directly from that snapshot. This reduces latency, eliminates unnecessary API traffic, and makes applications more resilient to network issues, rate limits, or temporary API outages.
Static site generation is a natural fit, but the same approach also works for SSR, ISR, server functions, background jobs, and other server-side workloads.
Choosing a mode
The client operates in one of two modes:
| Mode | Reads come from | Default |
|---|---|---|
'api' | The live Content Island REST API. | yes |
'snapshot' | A local snapshot file (snapshotPath). | — |
You set the mode when you create the client:
import { createClient } from '@content-island/api-client';
const client = createClient({ accessToken: 'YOUR_ACCESS_TOKEN', mode: 'snapshot',});In 'snapshot' mode reads are served from the local snapshot file with no request to the API and with drop-in parity to the live results. When mode is omitted, the client defaults to 'api' and behaves exactly as it always has.
The snapshot file
In snapshot mode the client reads from a snapshot file on disk. You point to it with the optional snapshotPath option:
const client = createClient({ accessToken: 'YOUR_ACCESS_TOKEN', mode: 'snapshot', snapshotPath: './content-island-snapshot.json',});snapshotPath is optional and defaults to './content-island-snapshot.json'. It is not required: omitting it simply uses the default path. An error is raised only when the file at that path is missing, unreadable or invalid — never just because the option was left out.
Where the mode is set
The mode lives in two places:
-
Client level — the
modeoption oncreateClientsets the default for all reads. -
Per read — the five read methods accept a
modeinside the query object, affecting only that one call:getContentListgetContentgetRawContentListgetRawContentgetContentListSize
// The client reads from the API by default;// this one read is served from the snapshot:const client = createClient({ accessToken: 'YOUR_ACCESS_TOKEN', snapshotPath: './content-island-snapshot.json',});
const posts = await client.getContentList({ contentType: 'post', mode: 'snapshot', // this read only});getProject does run in snapshot mode — it’s served from the snapshot’s project object (languages included). It simply takes no query-params object, so there is no per-read mode for it: it always follows the client-level mode. To inspect the on-disk snapshot without loading the project, use getSnapshotInfo.
onRelatedContentMeta
onRelatedContentMeta is an optional per-query callback that reports how the related-content resolution went. It runs on the same five read methods and has the signature:
onRelatedContentMeta: ({ resolvedDepth, partial }) => void;It is invoked exactly once per call:
resolvedDepth— how deep the related-content resolution actually went.partial—truewhen a depth or resolution-budget cap left part of the related-content graph unresolved.
The callback works in both modes, and for the same data and query it reports identical values:
- In api mode, the values come from the
X-Related-Content-Resolved-DepthandX-Related-Content-Partialresponse headers (an absentpartialheader is treated asfalse). - In snapshot mode, the values come from the local breadth-first resolution over the snapshot.
const list = await client.getContentList({ contentType: 'post', includeRelatedContent: 'all', onRelatedContentMeta: ({ resolvedDepth, partial }) => { console.log({ resolvedDepth, partial }); },});When onRelatedContentMeta is omitted, behaviour and return shapes are unchanged. Like mode, it is a client-only option and is never serialized to the query string.
Writes are not available in snapshot mode
A snapshot-mode client serves reads only. A snapshot is a frozen, read-only copy of your content, so there is nothing to write to. Every write and schema-management method rejects with an ApiClientError whose code is SNAPSHOT_MODE, and it performs no network call — the rejection happens locally, before any request would be made:
createContentpublishContentupdateContentFieldValueuploadMediacreateModelupdateModeldeleteModelcreateEnumupdateEnumdeleteEnum
import { createClient, ApiClientError } from '@content-island/api-client';
const client = createClient({ accessToken: 'YOUR_ACCESS_TOKEN', mode: 'snapshot',});
try { await client.createContent(/* … */);} catch (error) { if (error instanceof ApiClientError && error.code === 'SNAPSHOT_MODE') { // Writes are rejected in snapshot mode — no request was sent. }}If you need to read from a snapshot and also write, create a second, separate client in 'api' mode for the writes, authenticated with a Write Token:
import { createClient } from '@content-island/api-client';
// Reader: serves reads from the local snapshot.const reader = createClient({ accessToken: 'YOUR_READ_TOKEN', mode: 'snapshot',});
// Writer: a separate api-mode client for writes.const writer = createClient({ accessToken: 'YOUR_WRITE_TOKEN', // mode defaults to 'api'});
const posts = await reader.getContentList({ contentType: 'post' });await writer.createContent(/* … */);Exporting a snapshot from the CLI
Generate a snapshot with the content-island export command. It exports your project’s content and writes it to disk:
npx content-island export \ --access-token <your-read-token> \ --snapshot-path ./content-island-snapshot.json--snapshot-path is optional; if you omit it, the snapshot is written to ./content-island-snapshot.json, relative to the current working directory.
The command mirrors the exportSnapshot() programmatic API one-to-one (the CLI is built on it). Its flags are:
| Flag | Description | Default |
|---|---|---|
--access-token | Required. The access token for the export. Falls back to the env var below. | env CONTENT_ISLAND_ACCESS_TOKEN |
--snapshot-path | Where the snapshot file is written. | ./content-island-snapshot.json |
--domain | Override the Content Island domain (for self-hosted instances). | — |
--secure-protocol | Use HTTPS explicitly (this is the default). | HTTPS |
--no-secure-protocol | Use HTTP instead of HTTPS (local / self-hosted instances). | — |
--api-version | Override the API version used for the export. | — |
The export uses HTTPS by default. Pass --no-secure-protocol only for a local or self-hosted instance served over plain HTTP:
npx content-island export \ --access-token <your-read-token> \ --domain localhost:3000 \ --no-secure-protocolOn success the command prints a summary — the output path, the file size, the exportedAt timestamp and the view — and exits 0.
If the token is missing, the request fails, or the downloaded snapshot fails validation, the command writes the error to stderr (standard error) and exits with a non-zero exit code. Because it writes through a temporary file that is renamed into place only after a successful, validated download, a failed run leaves no partial or invalid file at --snapshot-path: either you get a complete snapshot or the previous file is left untouched.
A common workflow
One common setup — it depends on your team and project — is api mode in local development and snapshot mode in production builds:
- Local dev (
mode: 'api') — always fresh content, no export step to remember. - Production build (
mode: 'snapshot') — fast, repeatable, with zero per-request network calls.
Drive the choice from an environment variable so the same code runs in both:
import { createClient } from '@content-island/api-client';
const client = createClient({ accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN, mode: process.env.NODE_ENV === 'production' ? 'snapshot' : 'api',});In production, generate the snapshot first (an export step), then run your build so it reads from the freshly exported file.
GitHub Action
This workflow exports a snapshot at build time, then builds your site in snapshot mode:
name: Build
on: push: branches: [main]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6
- uses: actions/setup-node@v6 with: node-version: 24
- run: npm ci
- name: Export content snapshot env: CONTENT_ISLAND_ACCESS_TOKEN: ${{ secrets.CONTENT_ISLAND_ACCESS_TOKEN }} run: npx content-island export
- name: Build (snapshot mode) env: NODE_ENV: production run: npm run build