@cimplify/cli 0.6.15 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/{add-6KEKFXOW.mjs → add-H6VQXQOI.mjs} +1 -1
  2. package/dist/{chunk-DEWCHGWU.mjs → chunk-AS3W7SUI.mjs} +81 -25
  3. package/dist/{chunk-JLSDFVML.mjs → chunk-BFT3GO4Q.mjs} +1 -1
  4. package/dist/{chunk-OSS47KZP.mjs → chunk-HXJZIFHC.mjs} +2 -2
  5. package/dist/dispatcher.mjs +9 -9
  6. package/dist/{doctor-WL25JRS3.mjs → doctor-7QL3TCOV.mjs} +2 -2
  7. package/dist/{explain-NY5A533M.mjs → explain-IVURNUO3.mjs} +4 -4
  8. package/dist/{introspect-J7GVKQK7.mjs → introspect-CFVKMSVE.mjs} +2 -2
  9. package/dist/{list-FXOFYX4H.mjs → list-56VJKIMT.mjs} +1 -1
  10. package/dist/{update-WNZKECFJ.mjs → update-IYLHJDL2.mjs} +1 -1
  11. package/package.json +2 -2
  12. package/templates/storefront-auto/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  13. package/templates/storefront-auto/CLAUDE.md +3 -2
  14. package/templates/storefront-auto/app/categories/[slug]/page.tsx +38 -16
  15. package/templates/storefront-auto/app/collections/[slug]/page.tsx +38 -16
  16. package/templates/storefront-auto/app/llms.txt/route.ts +12 -8
  17. package/templates/storefront-auto/app/page.tsx +2 -5
  18. package/templates/storefront-auto/app/products/[slug]/page.tsx +50 -20
  19. package/templates/storefront-auto/app/shop/page.tsx +9 -7
  20. package/templates/storefront-auto/app/shop/shop-client.tsx +1 -1
  21. package/templates/storefront-auto/app/sitemap-page/page.tsx +12 -8
  22. package/templates/storefront-auto/bun.lock +2 -2
  23. package/templates/storefront-auto/components/footer.tsx +0 -1
  24. package/templates/storefront-auto/lib/site.config.ts +1 -1
  25. package/templates/storefront-auto/next.config.ts +6 -5
  26. package/templates/storefront-auto/package.json +1 -1
  27. package/templates/storefront-bakery/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  28. package/templates/storefront-bakery/AGENTS.md +4 -3
  29. package/templates/storefront-bakery/CLAUDE.md +3 -2
  30. package/templates/storefront-bakery/app/categories/[slug]/page.tsx +38 -16
  31. package/templates/storefront-bakery/app/collections/[slug]/page.tsx +38 -16
  32. package/templates/storefront-bakery/app/llms.txt/route.ts +12 -8
  33. package/templates/storefront-bakery/app/page.tsx +18 -8
  34. package/templates/storefront-bakery/app/shop/page.tsx +9 -7
  35. package/templates/storefront-bakery/app/shop/shop-client.tsx +1 -1
  36. package/templates/storefront-bakery/app/sitemap-page/page.tsx +12 -8
  37. package/templates/storefront-bakery/bun.lock +2 -2
  38. package/templates/storefront-bakery/components/footer.tsx +0 -1
  39. package/templates/storefront-bakery/lib/site.config.ts +1 -1
  40. package/templates/storefront-bakery/next.config.ts +6 -5
  41. package/templates/storefront-bakery/package.json +1 -1
  42. package/templates/storefront-fashion/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  43. package/templates/storefront-fashion/CLAUDE.md +3 -2
  44. package/templates/storefront-fashion/app/categories/[slug]/page.tsx +38 -16
  45. package/templates/storefront-fashion/app/collections/[slug]/page.tsx +38 -16
  46. package/templates/storefront-fashion/app/llms.txt/route.ts +12 -8
  47. package/templates/storefront-fashion/app/page.tsx +22 -9
  48. package/templates/storefront-fashion/app/products/[slug]/page.tsx +50 -20
  49. package/templates/storefront-fashion/app/shop/page.tsx +9 -7
  50. package/templates/storefront-fashion/app/shop/shop-client.tsx +1 -1
  51. package/templates/storefront-fashion/app/sitemap-page/page.tsx +12 -8
  52. package/templates/storefront-fashion/bun.lock +2 -2
  53. package/templates/storefront-fashion/components/footer.tsx +0 -1
  54. package/templates/storefront-fashion/lib/site.config.ts +1 -1
  55. package/templates/storefront-fashion/next.config.ts +6 -5
  56. package/templates/storefront-fashion/package.json +1 -1
  57. package/templates/storefront-grocery/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  58. package/templates/storefront-grocery/CLAUDE.md +3 -2
  59. package/templates/storefront-grocery/app/categories/[slug]/page.tsx +38 -16
  60. package/templates/storefront-grocery/app/collections/[slug]/page.tsx +38 -16
  61. package/templates/storefront-grocery/app/llms.txt/route.ts +12 -8
  62. package/templates/storefront-grocery/app/page.tsx +18 -8
  63. package/templates/storefront-grocery/app/shop/page.tsx +9 -7
  64. package/templates/storefront-grocery/app/shop/shop-client.tsx +1 -1
  65. package/templates/storefront-grocery/app/sitemap-page/page.tsx +12 -8
  66. package/templates/storefront-grocery/bun.lock +2 -2
  67. package/templates/storefront-grocery/components/footer.tsx +0 -1
  68. package/templates/storefront-grocery/lib/site.config.ts +1 -1
  69. package/templates/storefront-grocery/next.config.ts +6 -5
  70. package/templates/storefront-grocery/package.json +1 -1
  71. package/templates/storefront-pharmacy/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  72. package/templates/storefront-pharmacy/CLAUDE.md +3 -2
  73. package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +38 -16
  74. package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +38 -16
  75. package/templates/storefront-pharmacy/app/llms.txt/route.ts +12 -8
  76. package/templates/storefront-pharmacy/app/page.tsx +2 -5
  77. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +50 -20
  78. package/templates/storefront-pharmacy/app/shop/page.tsx +9 -7
  79. package/templates/storefront-pharmacy/app/shop/shop-client.tsx +1 -1
  80. package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +12 -8
  81. package/templates/storefront-pharmacy/bun.lock +2 -2
  82. package/templates/storefront-pharmacy/components/footer.tsx +0 -1
  83. package/templates/storefront-pharmacy/lib/site.config.ts +1 -1
  84. package/templates/storefront-pharmacy/next.config.ts +6 -5
  85. package/templates/storefront-pharmacy/package.json +1 -1
  86. package/templates/storefront-restaurant/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  87. package/templates/storefront-restaurant/CLAUDE.md +3 -2
  88. package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +38 -16
  89. package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +38 -16
  90. package/templates/storefront-restaurant/app/llms.txt/route.ts +12 -8
  91. package/templates/storefront-restaurant/app/page.tsx +18 -8
  92. package/templates/storefront-restaurant/app/reservations/page.tsx +6 -6
  93. package/templates/storefront-restaurant/app/shop/page.tsx +9 -7
  94. package/templates/storefront-restaurant/app/shop/shop-client.tsx +1 -1
  95. package/templates/storefront-restaurant/app/sitemap-page/page.tsx +12 -8
  96. package/templates/storefront-restaurant/bun.lock +2 -2
  97. package/templates/storefront-restaurant/components/footer.tsx +0 -1
  98. package/templates/storefront-restaurant/lib/site.config.ts +1 -1
  99. package/templates/storefront-restaurant/next.config.ts +6 -5
  100. package/templates/storefront-restaurant/package.json +1 -1
  101. package/templates/storefront-retail/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  102. package/templates/storefront-retail/CLAUDE.md +3 -2
  103. package/templates/storefront-retail/app/categories/[slug]/page.tsx +38 -16
  104. package/templates/storefront-retail/app/collections/[slug]/page.tsx +38 -16
  105. package/templates/storefront-retail/app/llms.txt/route.ts +12 -8
  106. package/templates/storefront-retail/app/page.tsx +22 -9
  107. package/templates/storefront-retail/app/products/[slug]/page.tsx +50 -20
  108. package/templates/storefront-retail/app/shop/page.tsx +9 -7
  109. package/templates/storefront-retail/app/shop/shop-client.tsx +1 -1
  110. package/templates/storefront-retail/app/sitemap-page/page.tsx +12 -8
  111. package/templates/storefront-retail/bun.lock +2 -2
  112. package/templates/storefront-retail/components/footer.tsx +0 -1
  113. package/templates/storefront-retail/lib/site.config.ts +1 -1
  114. package/templates/storefront-retail/next.config.ts +6 -5
  115. package/templates/storefront-retail/package.json +1 -1
  116. package/templates/storefront-services/.claude/skills/cimplify-storefront/SKILL.md +34 -18
  117. package/templates/storefront-services/CLAUDE.md +3 -2
  118. package/templates/storefront-services/app/book/page.tsx +6 -6
  119. package/templates/storefront-services/app/categories/[slug]/page.tsx +38 -16
  120. package/templates/storefront-services/app/collections/[slug]/page.tsx +38 -16
  121. package/templates/storefront-services/app/llms.txt/route.ts +12 -8
  122. package/templates/storefront-services/app/page.tsx +18 -8
  123. package/templates/storefront-services/app/shop/page.tsx +9 -7
  124. package/templates/storefront-services/app/shop/shop-client.tsx +1 -1
  125. package/templates/storefront-services/app/sitemap-page/page.tsx +12 -8
  126. package/templates/storefront-services/bun.lock +2 -2
  127. package/templates/storefront-services/components/footer.tsx +0 -1
  128. package/templates/storefront-services/lib/site.config.ts +1 -1
  129. package/templates/storefront-services/next.config.ts +6 -5
  130. package/templates/storefront-services/package.json +1 -1
  131. package/templates/storefront-auto/app/api/revalidate/route.ts +0 -5
  132. package/templates/storefront-bakery/app/api/revalidate/route.ts +0 -5
  133. package/templates/storefront-fashion/app/api/revalidate/route.ts +0 -5
  134. package/templates/storefront-grocery/app/api/revalidate/route.ts +0 -5
  135. package/templates/storefront-pharmacy/app/api/revalidate/route.ts +0 -5
  136. package/templates/storefront-restaurant/app/api/revalidate/route.ts +0 -5
  137. package/templates/storefront-retail/app/api/revalidate/route.ts +0 -5
  138. package/templates/storefront-services/app/api/revalidate/route.ts +0 -5
@@ -1,6 +1,5 @@
1
1
  import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
- import { cacheTag, cacheLife } from "next/cache";
4
3
  import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
4
  import { Hero } from "@/components/hero";
6
5
  import { CollectionStrip } from "@/components/collection-strip";
@@ -12,22 +11,33 @@ export const metadata: Metadata = {
12
11
  description: brand.description,
13
12
  };
14
13
 
15
- async function getHomeData() {
16
- "use cache";
17
- cacheTag(tags.collections(), tags.categories(), tags.products());
18
- cacheLife("hours");
14
+ export const revalidate = 3600;
19
15
 
16
+ async function getHomeData() {
20
17
  const client = getServerClient();
21
18
  const [colRes, catRes] = await Promise.all([
22
- client.catalogue.getCollections(),
23
- client.catalogue.getCategories(),
19
+ client.catalogue.getCollections({
20
+ cacheOptions: { revalidate: 3600, tags: [tags.collections()] },
21
+ }),
22
+ client.catalogue.getCategories({
23
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
24
+ }),
24
25
  ]);
25
26
  const collections = colRes.ok ? colRes.value : [];
26
27
  const categories = catRes.ok ? catRes.value : [];
27
28
 
28
29
  const collectionsWithProducts = await Promise.all(
29
30
  collections.map(async (col) => {
30
- const r = await client.catalogue.getCollectionProducts(col.id);
31
+ const r = await client.catalogue.getCollectionProducts(
32
+ col.id,
33
+ undefined,
34
+ {
35
+ cacheOptions: {
36
+ revalidate: 3600,
37
+ tags: [tags.collectionProducts(col.id)],
38
+ },
39
+ },
40
+ );
31
41
  const items = r.ok
32
42
  ? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
33
43
  : [];
@@ -1,6 +1,5 @@
1
1
  import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
- import { cacheTag, cacheLife } from "next/cache";
4
3
  import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
4
  import { ReservationsClient } from "./reservations-client";
6
5
  import { brand } from "@/lib/brand";
@@ -10,13 +9,14 @@ export const metadata: Metadata = {
10
9
  description: "Pick a date, time, and party size. We'll hold your table.",
11
10
  };
12
11
 
13
- async function getSeatingOptions(): Promise<Product[]> {
14
- "use cache";
15
- cacheTag(tags.products());
16
- cacheLife("hours");
12
+ export const revalidate = 3600;
17
13
 
14
+ async function getSeatingOptions(): Promise<Product[]> {
18
15
  const client = getServerClient();
19
- const r = await client.catalogue.getProducts({ limit: 50 });
16
+ const r = await client.catalogue.getProducts(
17
+ { limit: 50 },
18
+ { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
19
+ );
20
20
  if (!r.ok) return [];
21
21
  // Reservation options live as service products in the catalogue (e.g.
22
22
  // "Two-top", "Four-top", "Long table"). Filter to type=service.
@@ -1,5 +1,4 @@
1
1
  import type { Metadata } from "next";
2
- import { cacheTag, cacheLife } from "next/cache";
3
2
  import { getServerClient, tags } from "@cimplify/sdk/server";
4
3
  import { ShopClient } from "./shop-client";
5
4
  import { brand } from "@/lib/brand";
@@ -9,15 +8,18 @@ export const metadata: Metadata = {
9
8
  description: brand.description,
10
9
  };
11
10
 
12
- async function getShopData() {
13
- "use cache";
14
- cacheTag(tags.products(), tags.categories());
15
- cacheLife("hours");
11
+ export const revalidate = 3600;
16
12
 
13
+ async function getShopData() {
17
14
  const client = getServerClient();
18
15
  const [p, c] = await Promise.all([
19
- client.catalogue.getProducts({ limit: 50 }),
20
- client.catalogue.getCategories(),
16
+ client.catalogue.getProducts(
17
+ { limit: 50 },
18
+ { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
19
+ ),
20
+ client.catalogue.getCategories({
21
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
22
+ }),
21
23
  ]);
22
24
  return {
23
25
  products: p.ok ? p.value.items : [],
@@ -6,7 +6,7 @@ import { StoreProductCard } from "@/components/store-product-card";
6
6
 
7
7
  /**
8
8
  * Client island for the shop page. Server-side fetches all products and
9
- * categories (cached via `'use cache'` in `app/shop/page.tsx`), then hands
9
+ * categories (ISR-cached in `app/shop/page.tsx`), then hands
10
10
  * them to `<CataloguePage>` which owns the interactive filter / sort state.
11
11
  */
12
12
  export function ShopClient({
@@ -1,6 +1,5 @@
1
1
  import type { Metadata } from "next";
2
2
  import Link from "next/link";
3
- import { cacheTag, cacheLife } from "next/cache";
4
3
  import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
4
  import { brand } from "@/lib/brand";
6
5
 
@@ -9,6 +8,8 @@ export const metadata: Metadata = {
9
8
  description: "A human-readable index of every page on this site.",
10
9
  };
11
10
 
11
+ export const revalidate = 3600;
12
+
12
13
  interface SitemapData {
13
14
  products: { slug: string; name: string }[];
14
15
  categories: { slug: string; name: string }[];
@@ -16,15 +17,18 @@ interface SitemapData {
16
17
  }
17
18
 
18
19
  async function getSitemap(): Promise<SitemapData> {
19
- "use cache";
20
- cacheTag(tags.products(), tags.categories(), tags.collections());
21
- cacheLife("hours");
22
-
23
20
  const client = getServerClient();
24
21
  const [pRes, cRes, colRes] = await Promise.all([
25
- client.catalogue.getProducts({ limit: 500 }),
26
- client.catalogue.getCategories(),
27
- client.catalogue.getCollections(),
22
+ client.catalogue.getProducts(
23
+ { limit: 500 },
24
+ { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
25
+ ),
26
+ client.catalogue.getCategories({
27
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
28
+ }),
29
+ client.catalogue.getCollections({
30
+ cacheOptions: { revalidate: 3600, tags: [tags.collections()] },
31
+ }),
28
32
  ]);
29
33
  return {
30
34
  products: (pRes.ok ? pRes.value.items : []).map((p: Product) => ({
@@ -5,7 +5,7 @@
5
5
  "": {
6
6
  "name": "__STOREFRONT_NAME__",
7
7
  "dependencies": {
8
- "@cimplify/sdk": "^0.49.1",
8
+ "@cimplify/sdk": "^0.54.0",
9
9
  "next": "^16.2.6",
10
10
  "react": "^19.0.0",
11
11
  "react-dom": "^19.0.0",
@@ -31,7 +31,7 @@
31
31
 
32
32
  "@base-ui/utils": ["@base-ui/utils@0.2.9", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw=="],
33
33
 
34
- "@cimplify/sdk": ["@cimplify/sdk@0.49.1", "", { "dependencies": { "@base-ui/react": "^1.4.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "libphonenumber-js": "1.12.41", "react-day-picker": "^9.14.0", "tailwind-merge": "^3.5.0", "zod": "^4.4.3" }, "peerDependencies": { "@paystack/inline-js": "^2.22.8", "msw": ">=2.0.0", "react": ">=17.0.0", "vitest": ">=2.0.0" }, "optionalPeers": ["@paystack/inline-js", "msw", "react", "vitest"], "bin": { "cimplify-mock": "dist/mock/cli.mjs" } }, "sha512-f105Jd129KQSK747SjiJAxiwKhY7S82D9v9giJTg4ckhdQakc48V+B/TseP2LwtXZDJ71iRH5W+nod0/BHW42A=="],
34
+ "@cimplify/sdk": ["@cimplify/sdk@0.54.0", "", { "dependencies": { "@base-ui/react": "^1.4.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "libphonenumber-js": "1.12.41", "react-day-picker": "^9.14.0", "tailwind-merge": "^3.5.0", "zod": "^4.4.3" }, "peerDependencies": { "@paystack/inline-js": "^2.22.8", "msw": ">=2.0.0", "react": ">=17.0.0", "vitest": ">=2.0.0" }, "optionalPeers": ["@paystack/inline-js", "msw", "react", "vitest"], "bin": { "cimplify-mock": "dist/mock/cli.mjs" } }, "sha512-YUN/lOqViHci1v9USWWwAWty29pEXBdLTuKarSl8EpafWh5jkdGZ1Q+Ne9/bfgTZmR8iTUltA8U7glV2FoRSgg=="],
35
35
 
36
36
  "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
37
37
 
@@ -48,7 +48,6 @@ const FALLBACK_ICON = (
48
48
  );
49
49
 
50
50
  export async function Footer() {
51
- "use cache";
52
51
  const year = new Date().getFullYear();
53
52
  return (
54
53
  <footer className="mt-12 px-6 sm:px-8 py-10 text-xs text-muted-foreground border-t border-border bg-card">
@@ -2,6 +2,6 @@
2
2
  * Canonical absolute URL. The platform overwrites this at build time with the
3
3
  * storefront's primary domain; the default only applies to local builds. It's
4
4
  * per-deploy config, not per-request, so it stays a static constant — keeping
5
- * the root layout prerenderable under `cacheComponents`.
5
+ * the root layout prerenderable.
6
6
  */
7
7
  export const SITE_URL = "https://example.com";
@@ -19,13 +19,14 @@ if (STOREFRONT_URL === "http://127.0.0.1:8787") {
19
19
  );
20
20
  }
21
21
 
22
+ // Cache Components ('use cache' + cacheTag/cacheLife) require Node-specific
23
+ // setTimeout atomicity and serialize a postponed state that routinely exceeds
24
+ // CF Workers' 128MB zlib limit. We're on Cloudflare Workers via opennext, so
25
+ // we stay on Next 16's "Previous Model" — `fetch.next.{revalidate,tags}` via
26
+ // the SDK's `cacheOptions`, plus `export const revalidate` per page. See
27
+ // https://nextjs.org/docs/app/guides/caching-without-cache-components.
22
28
  const nextConfig: NextConfig = {
23
- // Enable Next 16's `cacheComponents` mode so we can use `'use cache'` +
24
- // `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
25
- // dynamic Suspense boundaries fill in when ready.
26
- cacheComponents: true,
27
29
  async redirects() {
28
- // Config-level so cacheComponents needn't prerender a redirect()-only page.
29
30
  return [
30
31
  { source: "/login", destination: "/account", permanent: false },
31
32
  { source: "/signup", destination: "/account", permanent: false },
@@ -17,7 +17,7 @@
17
17
  "check": "bun run typecheck && bun run test:run"
18
18
  },
19
19
  "dependencies": {
20
- "@cimplify/sdk": "^0.49.1",
20
+ "@cimplify/sdk": "^0.54.0",
21
21
  "next": "^16.2.6",
22
22
  "react": "^19.0.0",
23
23
  "react-dom": "^19.0.0"
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: cimplify-storefront
3
- description: Build, customize, rebrand, or deploy a Cimplify-scaffolded storefront. Triggers when the user asks to create a storefront, rebrand a Cimplify template, change the palette, add a page, deploy to Cimplify, or works in a project containing `lib/brand.ts` and `next.config.ts` with `cacheComponents`.
3
+ description: Build, customize, rebrand, or deploy a Cimplify-scaffolded storefront. Triggers when the user asks to create a storefront, rebrand a Cimplify template, change the palette, add a page, deploy to Cimplify, or works in a project containing `lib/brand.ts` and `next.config.ts`.
4
4
  ---
5
5
 
6
6
  # Cimplify Storefront skill
7
7
 
8
- You're working on a project scaffolded from `cimplify init`. The architecture is opinionated and the rebrand surface is intentionally small. Read `AGENTS.md` at the project root for the file ↔ brand-field map for *this template's* industry; this skill gives you the playbook that's the same across all six.
8
+ You're working on a project scaffolded from `cimplify init`. The architecture is opinionated and the rebrand surface is intentionally small. Read `AGENTS.md` at the project root for the file ↔ brand-field map for *this template's* industry; this skill gives you the playbook that's the same across all eight.
9
9
 
10
10
  ## The contract — never break
11
11
 
12
12
  1. **`lib/brand.ts` is the only place for content edits.** Every visible string reads from this file. If a string isn't in `brand`, *add a field* to the `Brand` interface — don't hardcode it in a page or component.
13
13
  2. **`app/globals.css` `@theme { … }`** holds palette + radius + font references. To re-skin the entire site, edit only this block.
14
- 3. **Server Components are cached** via `'use cache'` + `cacheTag(tags.X())` + `cacheLife("hours")`. Don't downgrade them to `"use client"` to avoid an issue fix the issue.
15
- 4. **Client islands** (anything reading `useSearchParams`, `usePathname`, `useRouter`, `useState`) live behind `<Suspense>`.
16
- 5. **`cacheComponents: true`** in `next.config.ts` is non-negotiable. Don't turn it off.
14
+ 3. **Pages use ISR**, not Cache Components. Each page sets `export const revalidate = <seconds>` and reads from the SDK with `cacheOptions: { revalidate, tags }`. Don't add `'use cache'` / `cacheTag` / `cacheLife` — they require Node-specific runtime guarantees Cloudflare Workers doesn't provide, and their postponed state blows past CF's 128MB zlib limit.
15
+ 4. **`cacheComponents` stays OFF** in `next.config.ts`. The deploy target is Cloudflare Workers via opennext.
16
+ 5. **Client islands** (anything reading `useSearchParams`, `usePathname`, `useRouter`, `useState`) live behind `<Suspense>`.
17
17
  6. **`bun run test:run` (vitest)** is the canonical test runner. `bun test` will show false failures because Bun's `vi` shim is incomplete.
18
18
  7. **Cart, checkout, orders stay client.** They're session-bound. Don't try to SSR them.
19
19
 
@@ -57,7 +57,7 @@ Don't touch any other file. If the rebrand needs content not in the schema, add
57
57
 
58
58
  1. Build it as a Server Component in `components/` (or a client island in `*-client.tsx` if interactive).
59
59
  2. Read merchant copy from `brand`. Add new fields to the `Brand` interface if needed.
60
- 3. Wrap interactive bits in `<Suspense fallback={…}>` so the cached chrome streams.
60
+ 3. Wrap interactive bits in `<Suspense fallback={…}>` so static chrome streams first.
61
61
  4. Compose into the page.
62
62
 
63
63
  ### Wire a Server Action that mutates data
@@ -72,24 +72,39 @@ export async function createProduct(input: ProductInput) {
72
72
  }
73
73
  ```
74
74
 
75
- After every mutation, call the matching `revalidate*` helper from `@cimplify/sdk/server`: `revalidateProducts`, `revalidateProduct(id)`, `revalidateCategories`, `revalidateCategory(id)`, `revalidateCollections`, `revalidateCollection(id)`, `revalidateBusiness`.
75
+ After every mutation, call the matching `revalidate*` helper from `@cimplify/sdk/server`: `revalidateProducts`, `revalidateProduct(id)`, `revalidateCategories`, `revalidateCategory(id)`, `revalidateCollections`, `revalidateCollection(id)`, `revalidateBusiness`. These fire eviction events the central tag-cache worker picks up — it drops R2 entries and purges Cloudflare's edge.
76
76
 
77
- ### Add a Server Component data fetch
77
+ ### Add a Server Component data fetch (ISR)
78
78
 
79
79
  ```ts
80
- import { cacheTag, cacheLife } from "next/cache";
81
80
  import { getServerClient, tags } from "@cimplify/sdk/server";
82
81
 
83
- async function getX() {
84
- "use cache";
85
- cacheTag(tags.products());
86
- cacheLife("hours");
87
- const r = await getServerClient().catalogue.getProducts({ limit: 24 });
88
- if (!r.ok) throw new Error(r.error.message);
82
+ export const revalidate = 3600; // page-level baseline
83
+
84
+ async function getProducts() {
85
+ const r = await getServerClient().catalogue.getProducts({
86
+ limit: 24,
87
+ cacheOptions: { revalidate: 3600, tags: [tags.products()] },
88
+ });
89
+ if (!r.ok) {
90
+ // Soft-render on transient errors so the page degrades gracefully
91
+ // instead of hard-failing with React #419. Only call notFound() on
92
+ // genuine 404 from origin.
93
+ if (r.error.code === "NOT_FOUND") notFound();
94
+ return [];
95
+ }
89
96
  return r.value.items;
90
97
  }
91
98
  ```
92
99
 
100
+ For `[slug]` routes, add `generateStaticParams` with a placeholder fallback so the page is statically prerenderable even with no upstream data yet:
101
+
102
+ ```ts
103
+ export async function generateStaticParams() {
104
+ return [{ slug: "__placeholder__" }];
105
+ }
106
+ ```
107
+
93
108
  ### Eject an SDK component for deeper customization
94
109
 
95
110
  Try `classNames`, `renderImage`, `renderLink`, slot props first. If those run out:
@@ -116,10 +131,11 @@ cimplify domains add my-store.com
116
131
  ## Pitfalls — explicit ❌ list
117
132
 
118
133
  - ❌ Hardcoding any visible string in a page or component. Always `brand.X`.
119
- - ❌ Disabling `cacheComponents: true` to silence a warning. Fix the warning by wrapping the dynamic call in `<Suspense>`.
120
- - ❌ Using `unstable_cache` — Next 16's canonical primitive is `'use cache'` + `cacheTag` + `cacheLife`.
134
+ - ❌ Adding `'use cache'`, `cacheTag`, `cacheLife`, or enabling `cacheComponents: true`. We deploy to Cloudflare Workers; postponed state exceeds the 128MB zlib limit. Use `cacheOptions` on SDK reads + `export const revalidate` per page.
135
+ - ❌ Using `unstable_cache` — removed in Next 16. Use SDK `cacheOptions` instead.
121
136
  - ❌ Bypassing `getServerClient()` and calling `createCimplifyClient` directly in a Server Component — loses per-request memoization.
122
137
  - ❌ Mutating data without calling the matching `revalidate*` helper.
138
+ - ❌ Calling `notFound()` on transient SDK errors — causes React #419 mid-stream. Check the `Result.error.code === "NOT_FOUND"` first; otherwise soft-render a skeleton.
123
139
  - ❌ Adding an `app/error.tsx` handler that calls `reset()` without logging — silently swallowed errors hide bugs.
124
140
  - ❌ Running `bun test` and reporting failures. Use `bun run test:run` (vitest).
125
141
  - ❌ Editing the per-template `AGENTS.md` to remove notes about TODOs (contact form, newsletter fake submits) — they're real.
@@ -132,7 +148,7 @@ cimplify domains add my-store.com
132
148
  | Architectural rules | `AGENTS.md` at project root + this skill |
133
149
  | Running locally | `bun dev` (boots mock + Next together) |
134
150
  | Switching mock seed | edit `dev:mock` in `package.json` |
135
- | Full SDK reference | `docs/sdk/storefronts.md` in the Cimplify repo |
151
+ | Full SDK reference | `cimplify.dev/sdk/optimization`, `cimplify.dev/sdk/server` |
136
152
 
137
153
  ## What to do when the user asks something out-of-scope
138
154
 
@@ -17,6 +17,7 @@ That covers ~95% of what merchants ask for. For anything else, follow the `cimpl
17
17
  ## Don't do
18
18
 
19
19
  - Hardcode strings in pages or components.
20
- - Disable `cacheComponents: true` in `next.config.ts`.
21
- - Use `unstable_cache` Next 16's canonical primitive is `'use cache'`.
20
+ - Enable `cacheComponents: true` in `next.config.ts` — we're on Cloudflare Workers, where `'use cache'` postponed state blows past CF's 128MB zlib limit. This template stays on Next 16's "Previous Model" (ISR): `export const revalidate` per page + `cacheOptions: { revalidate, tags }` on SDK reads.
21
+ - Add `'use cache'`, `cacheTag`, or `cacheLife` anywhere. Use the SDK's `cacheOptions` instead.
22
+ - Use `unstable_cache` — it's gone in Next 16. Use SDK `cacheOptions` or `fetch.next.{revalidate,tags}`.
22
23
  - Run `bun test` (Bun's `vi` shim is incomplete) — use `bun run test:run`.
@@ -2,7 +2,6 @@ import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
3
  import Link from "next/link";
4
4
  import { notFound } from "next/navigation";
5
- import { cacheTag, cacheLife } from "next/cache";
6
5
  import {
7
6
  getServerClient,
8
7
  tags,
@@ -12,26 +11,46 @@ import {
12
11
  import { ListingClient } from "./listing-client";
13
12
  import { brand } from "@/lib/brand";
14
13
 
14
+ // See app/products/[slug]/page.tsx for the rationale on generateStaticParams.
15
+ export async function generateStaticParams() {
16
+ const r = await getServerClient().catalogue.getCategories();
17
+ if (!r.ok || r.value.length === 0) {
18
+ return [{ slug: "__placeholder__" }];
19
+ }
20
+ return r.value.map((c) => ({ slug: c.slug ?? c.id }));
21
+ }
22
+
23
+ export const revalidate = 3600;
24
+
15
25
  interface CategoryData {
16
26
  category: Category;
17
27
  products: Product[];
18
28
  }
19
29
 
20
- async function getCategory(slug: string): Promise<CategoryData | null> {
21
- "use cache";
22
- cacheTag(tags.categories());
23
- cacheLife("hours");
30
+ type CategoryResult =
31
+ | { ok: true; data: CategoryData }
32
+ | { ok: false; code: string };
24
33
 
34
+ async function getCategory(slug: string): Promise<CategoryResult> {
25
35
  const client = getServerClient();
26
- const catRes = await client.catalogue.getCategoryBySlug(slug);
27
- if (!catRes.ok) return null;
36
+ const catRes = await client.catalogue.getCategoryBySlug(slug, {
37
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
38
+ });
39
+ if (!catRes.ok) return { ok: false, code: catRes.error.code };
28
40
 
29
- cacheTag(tags.category(catRes.value.id), tags.categoryProducts(catRes.value.id));
30
- const r = await client.catalogue.getCategoryProducts(catRes.value.id);
41
+ const r = await client.catalogue.getCategoryProducts(catRes.value.id, undefined, {
42
+ cacheOptions: {
43
+ revalidate: 3600,
44
+ tags: [
45
+ tags.category(catRes.value.id),
46
+ tags.categoryProducts(catRes.value.id),
47
+ ],
48
+ },
49
+ });
31
50
  const products = r.ok
32
51
  ? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
33
52
  : [];
34
- return { category: catRes.value, products };
53
+ return { ok: true, data: { category: catRes.value, products } };
35
54
  }
36
55
 
37
56
  export async function generateMetadata({
@@ -40,8 +59,9 @@ export async function generateMetadata({
40
59
  params: Promise<{ slug: string }>;
41
60
  }): Promise<Metadata> {
42
61
  const { slug } = await params;
43
- const data = await getCategory(slug);
44
- if (!data) return {};
62
+ const result = await getCategory(slug);
63
+ if (!result.ok) return {};
64
+ const data = result.data;
45
65
  return {
46
66
  title: `${data.category.name} — ${brand.name}`,
47
67
  description: data.category.description ?? undefined,
@@ -66,10 +86,12 @@ async function CategoryContent({
66
86
  params: Promise<{ slug: string }>;
67
87
  }) {
68
88
  const { slug } = await params;
69
- const data = await getCategory(slug);
70
- if (!data) notFound();
71
-
72
- const { category, products } = data;
89
+ const result = await getCategory(slug);
90
+ if (!result.ok) {
91
+ if (result.code === "NOT_FOUND") notFound();
92
+ return <CategorySkeleton />;
93
+ }
94
+ const { category, products } = result.data;
73
95
  return (
74
96
  <>
75
97
  <section className="bg-foreground text-background relative overflow-hidden">
@@ -2,7 +2,6 @@ import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
3
  import Link from "next/link";
4
4
  import { notFound } from "next/navigation";
5
- import { cacheTag, cacheLife } from "next/cache";
6
5
  import {
7
6
  getServerClient,
8
7
  tags,
@@ -12,26 +11,46 @@ import {
12
11
  import { ListingClient } from "./listing-client";
13
12
  import { brand } from "@/lib/brand";
14
13
 
14
+ // See app/products/[slug]/page.tsx for the rationale on generateStaticParams.
15
+ export async function generateStaticParams() {
16
+ const r = await getServerClient().catalogue.getCollections();
17
+ if (!r.ok || r.value.length === 0) {
18
+ return [{ slug: "__placeholder__" }];
19
+ }
20
+ return r.value.map((c) => ({ slug: c.slug ?? c.id }));
21
+ }
22
+
23
+ export const revalidate = 3600;
24
+
15
25
  interface CollectionData {
16
26
  collection: Collection;
17
27
  products: Product[];
18
28
  }
19
29
 
20
- async function getCollection(slug: string): Promise<CollectionData | null> {
21
- "use cache";
22
- cacheTag(tags.collections());
23
- cacheLife("hours");
30
+ type CollectionResult =
31
+ | { ok: true; data: CollectionData }
32
+ | { ok: false; code: string };
24
33
 
34
+ async function getCollection(slug: string): Promise<CollectionResult> {
25
35
  const client = getServerClient();
26
- const colRes = await client.catalogue.getCollectionBySlug(slug);
27
- if (!colRes.ok) return null;
36
+ const colRes = await client.catalogue.getCollectionBySlug(slug, {
37
+ cacheOptions: { revalidate: 3600, tags: [tags.collections()] },
38
+ });
39
+ if (!colRes.ok) return { ok: false, code: colRes.error.code };
28
40
 
29
- cacheTag(tags.collection(colRes.value.id), tags.collectionProducts(colRes.value.id));
30
- const r = await client.catalogue.getCollectionProducts(colRes.value.id);
41
+ const r = await client.catalogue.getCollectionProducts(colRes.value.id, undefined, {
42
+ cacheOptions: {
43
+ revalidate: 3600,
44
+ tags: [
45
+ tags.collection(colRes.value.id),
46
+ tags.collectionProducts(colRes.value.id),
47
+ ],
48
+ },
49
+ });
31
50
  const products = r.ok
32
51
  ? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
33
52
  : [];
34
- return { collection: colRes.value, products };
53
+ return { ok: true, data: { collection: colRes.value, products } };
35
54
  }
36
55
 
37
56
  export async function generateMetadata({
@@ -40,8 +59,9 @@ export async function generateMetadata({
40
59
  params: Promise<{ slug: string }>;
41
60
  }): Promise<Metadata> {
42
61
  const { slug } = await params;
43
- const data = await getCollection(slug);
44
- if (!data) return {};
62
+ const result = await getCollection(slug);
63
+ if (!result.ok) return {};
64
+ const data = result.data;
45
65
  return {
46
66
  title: `${data.collection.name} — ${brand.name}`,
47
67
  description: data.collection.description ?? undefined,
@@ -66,10 +86,12 @@ async function CollectionContent({
66
86
  params: Promise<{ slug: string }>;
67
87
  }) {
68
88
  const { slug } = await params;
69
- const data = await getCollection(slug);
70
- if (!data) notFound();
71
-
72
- const { collection, products } = data;
89
+ const result = await getCollection(slug);
90
+ if (!result.ok) {
91
+ if (result.code === "NOT_FOUND") notFound();
92
+ return <CollectionSkeleton />;
93
+ }
94
+ const { collection, products } = result.data;
73
95
  return (
74
96
  <>
75
97
  <section className="bg-foreground text-background relative overflow-hidden">
@@ -1,18 +1,22 @@
1
- import { cacheTag, cacheLife } from "next/cache";
2
1
  import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
3
2
  import { brand } from "@/lib/brand";
4
3
  import { getSiteUrl } from "@/lib/site-url";
5
4
 
6
- async function buildLlmsTxt(SITE_URL: string): Promise<string> {
7
- "use cache";
8
- cacheTag(tags.products(), tags.categories(), tags.collections());
9
- cacheLife("hours");
5
+ export const revalidate = 3600;
10
6
 
7
+ async function buildLlmsTxt(SITE_URL: string): Promise<string> {
11
8
  const client = getServerClient();
12
9
  const [productsRes, categoriesRes, collectionsRes] = await Promise.all([
13
- client.catalogue.getProducts({ limit: 500 }),
14
- client.catalogue.getCategories(),
15
- client.catalogue.getCollections(),
10
+ client.catalogue.getProducts(
11
+ { limit: 500 },
12
+ { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
13
+ ),
14
+ client.catalogue.getCategories({
15
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
16
+ }),
17
+ client.catalogue.getCollections({
18
+ cacheOptions: { revalidate: 3600, tags: [tags.collections()] },
19
+ }),
16
20
  ]);
17
21
 
18
22
  const products: Product[] = productsRes.ok ? productsRes.value.items : [];
@@ -1,6 +1,5 @@
1
1
  import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
- import { cacheTag, cacheLife } from "next/cache";
4
3
  import { getServerClient, tags, type Collection, type Product } from "@cimplify/sdk/server";
5
4
  import { FeatureHero } from "@/components/feature-hero";
6
5
  import { CategoryTiles } from "@/components/category-tiles";
@@ -19,21 +18,26 @@ export const metadata: Metadata = {
19
18
  description: brand.description,
20
19
  };
21
20
 
21
+ export const revalidate = 3600;
22
+
22
23
  interface CollectionWithProducts {
23
24
  collection: Collection;
24
25
  products: Product[];
25
26
  }
26
27
 
27
28
  async function getHomeData() {
28
- "use cache";
29
- cacheTag(tags.collections(), tags.categories(), tags.products());
30
- cacheLife("hours");
31
-
32
29
  const client = getServerClient();
33
30
  const [colRes, catRes, productsRes] = await Promise.all([
34
- client.catalogue.getCollections(),
35
- client.catalogue.getCategories(),
36
- client.catalogue.getProducts({ limit: 12 }),
31
+ client.catalogue.getCollections({
32
+ cacheOptions: { revalidate: 3600, tags: [tags.collections()] },
33
+ }),
34
+ client.catalogue.getCategories({
35
+ cacheOptions: { revalidate: 3600, tags: [tags.categories()] },
36
+ }),
37
+ client.catalogue.getProducts(
38
+ { limit: 12 },
39
+ { cacheOptions: { revalidate: 3600, tags: [tags.products()] } },
40
+ ),
37
41
  ]);
38
42
  const collections = colRes.ok ? colRes.value : [];
39
43
  const categories = catRes.ok ? catRes.value : [];
@@ -41,7 +45,16 @@ async function getHomeData() {
41
45
 
42
46
  const collectionsWithProducts: CollectionWithProducts[] = await Promise.all(
43
47
  collections.map(async (col) => {
44
- const r = await client.catalogue.getCollectionProducts(col.id);
48
+ const r = await client.catalogue.getCollectionProducts(
49
+ col.id,
50
+ undefined,
51
+ {
52
+ cacheOptions: {
53
+ revalidate: 3600,
54
+ tags: [tags.collectionProducts(col.id)],
55
+ },
56
+ },
57
+ );
45
58
  const items = r.ok
46
59
  ? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
47
60
  : [];