@cimplify/cli 0.6.16 → 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.
- package/dist/{add-BBVSJ5ZJ.mjs → add-H6VQXQOI.mjs} +1 -1
- package/dist/{chunk-F5EGUNQZ.mjs → chunk-AS3W7SUI.mjs} +81 -25
- package/dist/{chunk-GL2J7272.mjs → chunk-BFT3GO4Q.mjs} +1 -1
- package/dist/{chunk-KCKMGRII.mjs → chunk-HXJZIFHC.mjs} +2 -2
- package/dist/dispatcher.mjs +9 -9
- package/dist/{doctor-T6NA3EMW.mjs → doctor-7QL3TCOV.mjs} +2 -2
- package/dist/{explain-22NV6OL6.mjs → explain-IVURNUO3.mjs} +4 -4
- package/dist/{introspect-QOW24PJF.mjs → introspect-CFVKMSVE.mjs} +2 -2
- package/dist/{list-JHUIIMC2.mjs → list-56VJKIMT.mjs} +1 -1
- package/dist/{update-IOMLDQEN.mjs → update-IYLHJDL2.mjs} +1 -1
- package/package.json +2 -2
- package/templates/storefront-auto/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-auto/CLAUDE.md +3 -2
- package/templates/storefront-auto/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-auto/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-auto/app/llms.txt/route.ts +13 -9
- package/templates/storefront-auto/app/page.tsx +3 -6
- package/templates/storefront-auto/app/products/[slug]/page.tsx +51 -21
- package/templates/storefront-auto/app/shop/page.tsx +10 -8
- package/templates/storefront-auto/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-auto/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-auto/bun.lock +2 -2
- package/templates/storefront-auto/components/footer.tsx +0 -1
- package/templates/storefront-auto/lib/site.config.ts +1 -1
- package/templates/storefront-auto/next.config.ts +6 -5
- package/templates/storefront-auto/package.json +1 -1
- package/templates/storefront-bakery/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-bakery/AGENTS.md +4 -3
- package/templates/storefront-bakery/CLAUDE.md +3 -2
- package/templates/storefront-bakery/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-bakery/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-bakery/app/llms.txt/route.ts +13 -9
- package/templates/storefront-bakery/app/page.tsx +19 -9
- package/templates/storefront-bakery/app/shop/page.tsx +10 -8
- package/templates/storefront-bakery/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-bakery/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-bakery/bun.lock +2 -2
- package/templates/storefront-bakery/components/footer.tsx +0 -1
- package/templates/storefront-bakery/lib/site.config.ts +1 -1
- package/templates/storefront-bakery/next.config.ts +6 -5
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-fashion/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-fashion/CLAUDE.md +3 -2
- package/templates/storefront-fashion/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-fashion/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-fashion/app/llms.txt/route.ts +13 -9
- package/templates/storefront-fashion/app/page.tsx +23 -10
- package/templates/storefront-fashion/app/products/[slug]/page.tsx +51 -21
- package/templates/storefront-fashion/app/shop/page.tsx +10 -8
- package/templates/storefront-fashion/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-fashion/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-fashion/bun.lock +2 -2
- package/templates/storefront-fashion/components/footer.tsx +0 -1
- package/templates/storefront-fashion/lib/site.config.ts +1 -1
- package/templates/storefront-fashion/next.config.ts +6 -5
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-grocery/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-grocery/CLAUDE.md +3 -2
- package/templates/storefront-grocery/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-grocery/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-grocery/app/llms.txt/route.ts +13 -9
- package/templates/storefront-grocery/app/page.tsx +19 -9
- package/templates/storefront-grocery/app/shop/page.tsx +10 -8
- package/templates/storefront-grocery/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-grocery/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-grocery/bun.lock +2 -2
- package/templates/storefront-grocery/components/footer.tsx +0 -1
- package/templates/storefront-grocery/lib/site.config.ts +1 -1
- package/templates/storefront-grocery/next.config.ts +6 -5
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-pharmacy/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-pharmacy/CLAUDE.md +3 -2
- package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-pharmacy/app/llms.txt/route.ts +13 -9
- package/templates/storefront-pharmacy/app/page.tsx +3 -6
- package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +51 -21
- package/templates/storefront-pharmacy/app/shop/page.tsx +10 -8
- package/templates/storefront-pharmacy/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-pharmacy/bun.lock +2 -2
- package/templates/storefront-pharmacy/components/footer.tsx +0 -1
- package/templates/storefront-pharmacy/lib/site.config.ts +1 -1
- package/templates/storefront-pharmacy/next.config.ts +6 -5
- package/templates/storefront-pharmacy/package.json +1 -1
- package/templates/storefront-restaurant/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-restaurant/CLAUDE.md +3 -2
- package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-restaurant/app/llms.txt/route.ts +13 -9
- package/templates/storefront-restaurant/app/page.tsx +19 -9
- package/templates/storefront-restaurant/app/reservations/page.tsx +7 -7
- package/templates/storefront-restaurant/app/shop/page.tsx +10 -8
- package/templates/storefront-restaurant/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-restaurant/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-restaurant/bun.lock +2 -2
- package/templates/storefront-restaurant/components/footer.tsx +0 -1
- package/templates/storefront-restaurant/lib/site.config.ts +1 -1
- package/templates/storefront-restaurant/next.config.ts +6 -5
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-retail/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-retail/CLAUDE.md +3 -2
- package/templates/storefront-retail/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-retail/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-retail/app/llms.txt/route.ts +13 -9
- package/templates/storefront-retail/app/page.tsx +23 -10
- package/templates/storefront-retail/app/products/[slug]/page.tsx +51 -21
- package/templates/storefront-retail/app/shop/page.tsx +10 -8
- package/templates/storefront-retail/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-retail/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-retail/bun.lock +2 -2
- package/templates/storefront-retail/components/footer.tsx +0 -1
- package/templates/storefront-retail/lib/site.config.ts +1 -1
- package/templates/storefront-retail/next.config.ts +6 -5
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-services/.claude/skills/cimplify-storefront/SKILL.md +34 -18
- package/templates/storefront-services/CLAUDE.md +3 -2
- package/templates/storefront-services/app/book/page.tsx +7 -7
- package/templates/storefront-services/app/categories/[slug]/page.tsx +39 -17
- package/templates/storefront-services/app/collections/[slug]/page.tsx +39 -17
- package/templates/storefront-services/app/llms.txt/route.ts +13 -9
- package/templates/storefront-services/app/page.tsx +19 -9
- package/templates/storefront-services/app/shop/page.tsx +10 -8
- package/templates/storefront-services/app/shop/shop-client.tsx +1 -1
- package/templates/storefront-services/app/sitemap-page/page.tsx +13 -9
- package/templates/storefront-services/bun.lock +2 -2
- package/templates/storefront-services/components/footer.tsx +0 -1
- package/templates/storefront-services/lib/site.config.ts +1 -1
- package/templates/storefront-services/next.config.ts +6 -5
- package/templates/storefront-services/package.json +1 -1
- package/templates/storefront-auto/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-bakery/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-fashion/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-grocery/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-pharmacy/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-restaurant/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-retail/app/api/revalidate/route.ts +0 -5
- package/templates/storefront-services/app/api/revalidate/route.ts +0 -5
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { Suspense } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import { CACHE_LIFE_DEFAULT, getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
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";
|
|
7
6
|
import { CategoryGrid } from "@/components/category-grid";
|
|
@@ -12,22 +11,33 @@ export const metadata: Metadata = {
|
|
|
12
11
|
description: brand.description,
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
"use cache";
|
|
17
|
-
cacheTag(tags.collections(), tags.categories(), tags.products());
|
|
18
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
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
|
-
|
|
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(
|
|
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
|
-
import {
|
|
3
|
-
import { CACHE_LIFE_DEFAULT, getServerClient, tags } from "@cimplify/sdk/server";
|
|
2
|
+
import { getServerClient, tags } from "@cimplify/sdk/server";
|
|
4
3
|
import { ShopClient } from "./shop-client";
|
|
5
4
|
import { brand } from "@/lib/brand";
|
|
6
5
|
|
|
@@ -9,15 +8,18 @@ export const metadata: Metadata = {
|
|
|
9
8
|
description: brand.description,
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
"use cache";
|
|
14
|
-
cacheTag(tags.products(), tags.categories());
|
|
15
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
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(
|
|
20
|
-
|
|
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
|
|
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,7 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import {
|
|
4
|
-
import { CACHE_LIFE_DEFAULT, getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
3
|
+
import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
5
4
|
import { brand } from "@/lib/brand";
|
|
6
5
|
|
|
7
6
|
export const metadata: Metadata = {
|
|
@@ -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(CACHE_LIFE_DEFAULT);
|
|
22
|
-
|
|
23
20
|
const client = getServerClient();
|
|
24
21
|
const [pRes, cRes, colRes] = await Promise.all([
|
|
25
|
-
client.catalogue.getProducts(
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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.
|
|
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
|
|
|
@@ -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
|
|
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 },
|
|
@@ -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
|
|
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
|
|
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. **
|
|
15
|
-
4.
|
|
16
|
-
5.
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
- ❌
|
|
120
|
-
- ❌ Using `unstable_cache` — Next 16
|
|
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 | `
|
|
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
|
-
-
|
|
21
|
-
-
|
|
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,9 +2,8 @@ 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,
|
|
9
8
|
type Category,
|
|
10
9
|
type Product,
|
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
44
|
-
if (!
|
|
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
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
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,9 +2,8 @@ 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,
|
|
9
8
|
type Collection,
|
|
10
9
|
type Product,
|
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
44
|
-
if (!
|
|
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
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
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 {
|
|
2
|
-
import { CACHE_LIFE_DEFAULT, getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
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
|
-
|
|
7
|
-
"use cache";
|
|
8
|
-
cacheTag(tags.products(), tags.categories(), tags.collections());
|
|
9
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
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(
|
|
14
|
-
|
|
15
|
-
|
|
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,7 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { Suspense } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import { CACHE_LIFE_DEFAULT, getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
3
|
+
import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
5
4
|
import { PharmacyHero } from "@/components/pharmacy-hero";
|
|
6
5
|
import { UrgentCtas } from "@/components/urgent-ctas";
|
|
7
6
|
import { SymptomFinder } from "@/components/symptom-finder";
|
|
@@ -17,11 +16,9 @@ export const metadata: Metadata = {
|
|
|
17
16
|
description: brand.description,
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
"use cache";
|
|
22
|
-
cacheTag(tags.products());
|
|
23
|
-
cacheLife(CACHE_LIFE_DEFAULT);
|
|
19
|
+
export const revalidate = 3600;
|
|
24
20
|
|
|
21
|
+
async function getHomeData() {
|
|
25
22
|
const client = getServerClient();
|
|
26
23
|
const productsRes = await client.catalogue.getProducts({ limit: 12 });
|
|
27
24
|
const allProducts: Product[] = productsRes.ok ? productsRes.value.items : [];
|