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.
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
| Option | Type | Description |
|---|---|---|
secretKey | string | Defaults to CIMPLIFY_SECRET_KEY, then NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY. |
apiUrl | string | Defaults to CIMPLIFY_API_URL, then https://storefronts.cimplify.io in prod / http://127.0.0.1:8787 in dev. |
locationId | string | Optional location-scoped reads. |
accessToken | string | Customer 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:
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:
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:
export const revalidate = 0on personalized routes (/account/*). Disables ISR entirely.cacheOptions: { revalidate: 0 }on individual reads if only one block on the page is personalized.- Split the page: fetch guest catalogue with
getServerClient()in the parent, render the signed-in widget inside a<Suspense>that usesgetAuthenticatedServerClient()withrevalidate: 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.
| Builder | Tag |
|---|---|
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> |
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.
"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
| Helper | Invalidates |
|---|---|
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.
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 : []}
/>
);
}"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
| Variable | Purpose |
|---|---|
CIMPLIFY_SECRET_KEY | Server key for getServerClient. Never expose to the browser. |
CIMPLIFY_API_URL | API base URL (defaults to production; set to http://127.0.0.1:8787 for the local mock). |
NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY | Browser-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
-
React overview Provider, components, hooks.
-
Revalidation flow End-to-end tag invalidation, how merchant mutations propagate to cached pages.
-
CLI env vars
cimplify env pushfor deployments.