cimplify
TypeScript SDK

Server Components

`@cimplify/sdk/server` is a Node-only entry for the Next.js App Router. It ships three things: a request-memoized client factory, a typed cache-tag scheme, and Server Action revalidation helpers.

Caching is Next's job. Set export const revalidate = N on each page and pass cacheOptions: { revalidate, tags } to SDK reads; the SDK forwards them as fetch().next.{revalidate,tags}. Invalidate from Server Actions via revalidateProducts() and friends. The SDK doesn't try to be a caching layer of its own.

Why ISR, not 'use cache': Cimplify templates use Next 16's Previous Model: stable, fully supported, works identically on every runtime. The 'use cache' directive (Cache Components) is still experimental and its runtime constraints don't suit hosted storefronts. cacheComponents should be off in next.config.ts.

getServerClient

Returns a standard CimplifyClient wrapped in React's cache(), so every Server Component in the same request shares a single instance. Reads server env vars (CIMPLIFY_SECRET_KEY, falling back to NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY; CIMPLIFY_API_URL) with friendly defaults.

app/page.tsx
import { getServerClient, tags } from "@cimplify/sdk/server";

export const revalidate = 3600;

export default async function Home() {
  const r = await getServerClient().catalogue.getProducts(
    { limit: 24 },
    { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
  );
  if (!r.ok) throw new Error(r.error.message);
  return <ul>{r.value.items.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}

Options

OptionTypeDescription
secretKeystringDefaults to CIMPLIFY_SECRET_KEY, then NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY.
apiUrlstringDefaults to CIMPLIFY_API_URL, then https://storefronts.cimplify.io in prod / http://127.0.0.1:8787 in dev.
locationIdstringOptional location-scoped reads.
accessTokenstringCustomer OAuth access token. When set, the client attaches Authorization: Bearer <token> so the API returns per-customer data (their orders, their assigned price list, their subscriptions). See Authenticated reads.

Authenticated reads

Without an accessToken, the client makes API calls with the storefront's public key only. Cimplify treats the request as a guest. To get per-customer data (orders, subscriptions, price-list pricing) on the server, pass the customer's access token.

The token is set into the cimplify_access HttpOnly cookie by the /auth/callback handler (handleRedirectCallback for the redirect flow, handleOidcCallback for modal/silent). Read it server-side and pass it to getServerClient:

lib/cimplify.ts
import { headers } from "next/headers";
import {
  getAccessTokenFromCookieHeader,
  getServerClient,
  type CimplifyClient,
} from "@cimplify/sdk/server";

const CLIENT_ID = process.env.CIMPLIFY_CLIENT_ID!;

export async function getAuthenticatedServerClient(): Promise<CimplifyClient> {
  const cookieHeader = (await headers()).get("cookie");
  const accessToken = getAccessTokenFromCookieHeader({ clientId: CLIENT_ID }, cookieHeader);
  return getServerClient({ accessToken: accessToken ?? undefined });
}

When the cookie is missing or expired, getAccessTokenFromCookieHeader returns null. The fall-through to accessToken: undefined gives you the guest client transparently, no error handling needed.

Use it from any Server Component:

app/account/orders/page.tsx
import { getAuthenticatedServerClient } from "@/lib/cimplify";

export const revalidate = 0;

export default async function OrdersPage() {
  const client = await getAuthenticatedServerClient();
  const r = await client.orders.list({ limit: 20 });
  if (!r.ok) throw new Error(r.error.message);
  return <pre>{JSON.stringify(r.value, null, 2)}</pre>;
}

The full Sign in with Cimplify guide walks through the end-to-end flow including caching guidance.

Caching personalized data

The default ISR cache key is (URL + params). It does not include the Authorization header. If you ISR-cache a personalized read, the first request fills the cache and every subsequent user sees that shopper's data.

Three safe patterns:

  1. export const revalidate = 0 on personalized routes (/account/*). Disables ISR entirely.
  2. cacheOptions: { revalidate: 0 } on individual reads if only one block on the page is personalized.
  3. Split the page: fetch guest catalogue with getServerClient() in the parent, render the signed-in widget inside a <Suspense> that uses getAuthenticatedServerClient() with revalidate: 0.

tags: cache-tag builders

Pass these on cacheOptions.tags when calling SDK reads. Tag strings are namespaced under cimplify: so they can't collide with consumer tags.

BuilderTag
tags.products()cimplify:products
tags.product(id)cimplify:product:<id>
tags.categories()cimplify:categories
tags.category(id)cimplify:category:<id>
tags.categoryProducts(id)cimplify:category:<id>:products
tags.collections()cimplify:collections
tags.collection(id)cimplify:collection:<id>
tags.collectionProducts(id)cimplify:collection:<id>:products
tags.business()cimplify:business
tags.locations()cimplify:locations
tags.orders(customerId)cimplify:orders:<customerId>
tags.order(id)cimplify:order:<id>
app/products/[slug]/page.tsx
import { notFound } from "next/navigation";
import { getServerClient, tags } from "@cimplify/sdk/server";

export const revalidate = 3600;

// generateStaticParams enumerates known slugs at build so the route emits
// cacheable response headers (s-maxage) instead of no-store. Without it, the
// [slug] route is treated as fully runtime-dynamic and CF edge skips it.
// Placeholder fallback keeps the build alive if the catalogue API isn't
// reachable at build time.
export async function generateStaticParams() {
  const r = await getServerClient().catalogue.getProducts({ limit: 10_000 });
  if (!r.ok || r.value.items.length === 0) return [{ slug: "__placeholder__" }];
  return r.value.items.map((p) => ({ slug: p.slug ?? p.id }));
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const r = await getServerClient().catalogue.getProductBySlug(
    slug,
    { cacheOptions: { revalidate: 3600, tags: [tags.product(slug), tags.products()] } },
  );
  if (!r.ok) notFound();
  return <pre>{JSON.stringify(r.value, null, 2)}</pre>;
}

Server Actions: revalidate*

After a write, call the matching revalidate* helper to invalidate every cached read tagged with that resource. The helpers wrap Next's revalidateTag so consumers don't have to learn the tag scheme.

app/actions.ts
"use server";
import { getServerClient, revalidateProducts, revalidateProduct } from "@cimplify/sdk/server";

export async function archiveProduct(productId: string) {
  const client = getServerClient();
  const r = await client.catalogue.archiveProduct(productId);
  if (!r.ok) throw new Error(r.error.message);

  await revalidateProduct(productId);
  await revalidateProducts();
}

Helpers

HelperInvalidates
revalidateProducts()Products list.
revalidateProduct(id)Single product + the products list (denormalized fields are usually embedded).
revalidateCategories()Categories list.
revalidateCategory(id)Category + its products + the list.
revalidateCollections()Collections list.
revalidateCollection(id)Collection + its products + the list.
revalidateBusiness()Business profile (name, currency, branding).
revalidateByTag(tag)Escape hatch; invalidate by raw tag.

Pre-fetching for client components

SSR-prefetch on the server, hand off as props to a client component. The client component skips its initial fetch and renders instantly.

app/shop/page.tsx
import { getServerClient, tags } from "@cimplify/sdk/server";
import ShopClient from "./shop-client";

export const revalidate = 3600;

export default async function Shop() {
  const client = getServerClient();
  const [products, categories] = await Promise.all([
    client.catalogue.getProducts(
      { limit: 24 },
      { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
    ),
    client.catalogue.getCategories({
      cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
    }),
  ]);
  return (
    <ShopClient
      products={products.ok ? products.value.items : []}
      categories={categories.ok ? categories.value : []}
    />
  );
}
app/shop/shop-client.tsx
"use client";
import { CataloguePage } from "@cimplify/sdk/react";
import type { Product, Category } from "@cimplify/sdk/server";

export default function ShopClient({
  products,
  categories,
}: {
  products: Product[];
  categories: Category[];
}) {
  return <CataloguePage products={products} categories={categories} pageSize={24} />;
}

Env vars

VariablePurpose
CIMPLIFY_SECRET_KEYServer key for getServerClient. Never expose to the browser.
CIMPLIFY_API_URLAPI base URL (defaults to production; set to http://127.0.0.1:8787 for the local mock).
NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEYBrowser-safe public key for client-side usage. The SDK's server entry also reads this as a fallback when CIMPLIFY_SECRET_KEY is unset.

Where next

On this page