Localess
Node.js

Reference

Assets, error handling, caching, and the full type and utility-function reference for @localess/client.

Assets

assetLink(asset, params?)

Generate a fully qualified URL for a content asset. Pass params to resize or convert the image — see Image Transforms.

import { localessClient } from "@localess/client";

const client = localessClient({ origin, spaceId, token });

// From a ContentAsset object
const url = client.assetLink(content.data.image);

// From the asset's uri (its ID, not a path)
const url = client.assetLink(content.data.image.uri);

// Resized and converted
const thumb = client.assetLink(content.data.image, { w: 400, f: 'avif' });

assetOriginalLink(asset) and assetDownloadLink(asset)

A bare assetLink(asset) URL is a rendition, not the uploaded file: still images are re-encoded at their format's default quality even with no parameters. When you need the exact bytes that were uploaded, or want the browser to save the file instead of displaying it, use these two methods:

const originalUrl = client.assetOriginalLink(content.data.image); // the file exactly as uploaded
const downloadUrl = client.assetDownloadLink(content.data.image); // served as an attachment

Neither accepts transform parameters. A request that adds any is rejected with 400. Downloads keep non-ASCII file names intact.

Error Handling

getLinks, getContentBySlug, getContentById, and getTranslations throw instead of returning empty data on failure. Two error classes cover the two ways a request fails — both carry attempts, because failures are retried first:

  • LocalessApiError — the API answered with a non-2xx status. Carries status, statusText, url, the parsed error body, and a hint.
  • LocalessNetworkError — no response arrived (DNS failure, refused connection, TLS error). Carries origin, url, hint, and the underlying error as cause.

Always wrap calls in try/catch:

import { LocalessApiError, LocalessNetworkError } from "@localess/client";

try {
  const content = await client.getContentBySlug('home');
} catch (error) {
  if (error instanceof LocalessApiError) {
    console.error(error.status, error.statusText, error.hint);
  } else if (error instanceof LocalessNetworkError) {
    console.error(`Could not reach ${error.origin}`, error.cause);
  }
}

Timeouts and Retries

Every request times out after timeoutMs — 15 seconds by default. Node's fetch has no timeout of its own, so without one a hung connection can stall a static build indefinitely.

Failed requests are retried with exponential backoff and full jitter, honouring a server's Retry-After up to maxDelayMs:

  • Retried — network failures, and the statuses in retryStatuses (default 408, 429, 500, 502, 503, 504).
  • Thrown immediately — every other status, including 401, 403 and 404.

Each attempt gets its own timeout, so the worst case is roughly attempts × timeoutMs plus backoff. Every fetching method also accepts a signal to cancel the call; an abort through it is not retried.

const client = localessClient({ origin, spaceId, token, timeoutMs: 5000, retry: { attempts: 5 } });

const content = await client.getContentBySlug('home', { signal: AbortSignal.timeout(10_000) });
retry optionDefaultDescription
attempts3Maximum requests per call, including the first. 1 disables retrying
baseDelayMs300Base delay for exponential backoff
maxDelayMs5000Upper bound on any single delay, including a Retry-After
retryStatuses[408, 429, 500, 502, 503, 504]Response statuses worth retrying

Caching

All API responses are cached by default using a TTL (time-to-live) cache. The cache key is the request URL with the token removed and its parameters sorted. You can configure caching when initializing the client.

// Default: 5-minute TTL cache
const client = localessClient({ origin, spaceId, token });

// Custom TTL in seconds (e.g., 10 minutes)
const client = localessClient({ origin, spaceId, token, cacheTTL: 600 });

// Disable caching entirely
const client = localessClient({ origin, spaceId, token, cacheTTL: false });

Custom cache

Pass any object with get, set and has as cache. The methods may return promises, so a Redis- or KV-backed cache works as is:

import { localessClient, type ICache } from "@localess/client";

const cache: ICache<unknown> = {
  get: (key) => redis.get(key).then((v) => (v ? JSON.parse(v) : undefined)),
  set: (key, value) => redis.set(key, JSON.stringify(value)).then(() => undefined),
  has: (key) => redis.exists(key).then((n) => n > 0),
};

const client = localessClient({ origin, spaceId, token, cache });

Cache keys don't include the token. A cache instance shared by two clients is shared across their tokens. That is fine when both tokens have the same permissions — but don't share one between a draft-capable token and a published-only one, or the published-only client is served cached drafts instead of getting a 403.

Framework fetch options

fetchInit passes framework options — such as Next.js's next: { revalidate } or a standard cache mode — into every fetch, and each fetching method accepts the same option per call. A request that carries fetchInit bypasses the client's own cache entirely, so the framework owns caching for it and a framework-level revalidation isn't masked by a second cache layer.

Type Reference

Content<T>

interface Content<T extends ContentData> extends ContentMetadata {
  locale: string;           // The locale actually served — may be the space's fallback
  data?: T;
  links?: Links;            // Populated when resolveLink: true
  references?: References;  // Populated when resolveReference: true
  assets?: Assets;          // Populated when resolveAsset: true
}

ContentMetadata

interface ContentMetadata {
  id: string;
  name: string;
  kind: 'FOLDER' | 'DOCUMENT';
  slug: string;
  fullSlug: string;
  parentSlug: string;
  publishedAt?: string;
  createdAt: string;
  updatedAt: string;
}

ContentData

Base type for all content schema data objects.

interface ContentDataSchema {
  _id: string;
  _schema: string;
}

interface ContentData extends ContentDataSchema {
  [field: string]: ContentDataField | undefined;
}

ContentAsset

interface ContentAsset {
  kind: 'ASSET';
  uri: string;
}
interface ContentLink {
  kind: 'LINK';
  type: 'url' | 'content';
  target: '_blank' | '_self';
  uri: string;
}

ContentReference

interface ContentReference {
  kind: 'REFERENCE';
  uri: string;
}

ContentRichText

interface ContentRichText {
  type?: string;
  content?: ContentRichText[];
}

Other Types

  • Links — A key-value map of content IDs to ContentMetadata objects.
  • References — A key-value map of reference IDs to Content objects.
  • Translations — A key-value map of translation keys to translated string values.
  • Assets — A key-value map of asset IDs to asset metadata (name, type, alt, width, height, size, duration).
  • AssetTransformParams — The params accepted by assetLink — see Image Transforms.

Utility Functions

FunctionReturnsDescription
isBrowser()booleanReturns true if code is running in a browser environment
isServer()booleanReturns true if code is running in a server/Node.js environment
isIframe()booleanReturns true if the page is rendered inside an iframe

isBrowser, isServer, isIframe, loadLocalessSync, localessEditable and localessEditableField still work from @localess/client, but they are deprecated re-exports and will be removed in a future major. Import them from @localess/live-preview instead.

Resolves a ContentLink field to a path or URL. Pass the links map from a response fetched with resolveLink: true:

import { findLink } from "@localess/client";

const href = findLink(content.links, content.data.cta.link);
// content link → '/' + fullSlug (or '/not-found' if missing from the map); url link → the URL as is

On this page