@silverassist/agents-toolkit
Advanced tools
+1
-1
| { | ||
| "name": "@silverassist/agents-toolkit", | ||
| "version": "2.5.0", | ||
| "version": "2.5.1", | ||
| "description": "Reusable AI agent prompts for development workflows with Jira integration — supports GitHub Copilot, Claude Code, and Codex", | ||
@@ -5,0 +5,0 @@ "author": "Santiago Ramirez", |
+1
-1
@@ -6,3 +6,3 @@ /** | ||
| export const VERSION = "2.5.0"; | ||
| export const VERSION = "2.5.1"; | ||
@@ -9,0 +9,0 @@ export const PROMPTS = { |
@@ -25,7 +25,23 @@ --- | ||
| 2. **A `POST` IS cacheable cross-request — but only with explicit `next` options.** | ||
| Next.js does not cache `POST` by default. Any read `fetch` to CCDS or the WP GraphQL endpoint — | ||
| GET or POST — must set `next: { revalidate, tags }`. The request body is part of the cache key, so | ||
| different filter bodies cache independently. | ||
| 2. **A `POST` read caches its DATA with `next` options — but the ROUTE still renders dynamically.** | ||
| Next.js does not cache `POST` by default; any read `fetch` (GET or POST) to CCDS or the WP GraphQL | ||
| endpoint must set `next: { revalidate, tags }` (the body is part of the cache key, so different | ||
| filter bodies cache independently). **However** (WEB-1069, Next 16) a page whose data comes from a | ||
| POST read still prerenders as `ƒ` (Dynamic) and serves `cache-control: private, no-store` even with | ||
| those options — the data-fetch cache and the route's static/dynamic classification are independent. | ||
| So `next: { revalidate, tags }` is **necessary but not sufficient** to make a POST-read page | ||
| CDN-cacheable; you must also apply a rendering/edge strategy (rule 2b). GET reads ISR natively and | ||
| need nothing extra. | ||
| 2b. **Make a POST-read page cacheable at the rendering/edge layer.** Pick one (lowest risk first): | ||
| - **CDN edge override (current/default):** `src/proxy.ts` matches city/community paths and sets | ||
| `Cache-Control: public, s-maxage=2592000, stale-while-revalidate=2592000` — the same header ISR | ||
| pages emit (see rule 7 `expireTime`), so the CDN policy is uniform. Origin stays dynamic; the CDN | ||
| caches the deterministic-per-URL response. No build-time risk. This is what WEB-1069 shipped. | ||
| - **`export const dynamic = "force-static"` (interim):** prerenders the route as real ISR, but a | ||
| bad CCDS record/response at build time caches a blank page (the WEB-1058 regression) — use only | ||
| when the page is fully null-safe and reads throw on 5xx at runtime. Validate per repo. | ||
| - **`cacheComponents: true` + `use cache` (strategic):** PPR migration; do not mix ad hoc with | ||
| route-segment `export const revalidate`. | ||
| 3. **Always pair `tags` with a `revalidate` duration.** `next: { tags }` alone either holds stale data | ||
@@ -38,7 +54,9 @@ indefinitely or (Next 16 default) does not cache at runtime at all. Use a `CACHE_DURATIONS`-style | ||
| 5. **Every cacheable route exports `revalidate`.** Suggested tiers: listing 24h–30d, detail/city 24h | ||
| (`86400`), state/landing 7d (`604800`), WP catch-all 7d. Do **not** add `revalidate` to | ||
| mutation/personalized routes (forms, thank-you, wizards). Prefer `export const revalidate` over | ||
| `dynamic = "force-static"` so a failed ISR revalidation preserves the last good cache instead of | ||
| overwriting it with a broken page. | ||
| 5. **Every cacheable route exports `revalidate`.** CCDS/WP data changes rarely and is refreshed | ||
| on-demand (webhooks + tags), so the default is intentionally **long: `2592000` (30d)** for CCDS | ||
| reads, `WP_CACHE_DURATIONS`, and page segments alike (WEB-1069). Shorter tiers (24h/7d) are fine | ||
| per-route for volatile sources. Do **not** add `revalidate` to mutation/personalized routes (forms, | ||
| thank-you, wizards). Default to `export const revalidate` over `dynamic = "force-static"` so a | ||
| failed ISR revalidation preserves the last good cache instead of overwriting it with a broken page | ||
| (force-static is still a valid POST-read option per rule 2b once null-safety + throw-on-5xx exist). | ||
@@ -49,4 +67,10 @@ 6. **On-demand revalidation dual-invalidates.** `/api/revalidate` must call `revalidateTag`/ | ||
| 7. **Image config** in `next.config`: set `minimumCacheTTL: 2592000` (30d), `qualities`, and | ||
| `formats: ["image/avif", "image/webp"]`. No malformed `remotePatterns` hostnames. | ||
| 7. **`next.config` cache config.** `expireTime` sets the CDN stale window: Next emits | ||
| `s-maxage=<revalidate>, stale-while-revalidate=<expireTime − revalidate>` for ISR pages, so | ||
| `expireTime` **must be ≥ the largest page `revalidate`**. Set `expireTime: 5184000` (60d) so a 30d | ||
| page yields `s-maxage=2592000, stale-while-revalidate=2592000` — the same header the proxy sets on | ||
| dynamic pages (rule 2b). (WEB-1069 bug: `expireTime: 86400` under a 30d revalidate = invalid stale | ||
| window.) Also set image `minimumCacheTTL: 31536000` (1y — optimized images are content-hashed and | ||
| never change), `qualities`, and `formats: ["image/avif", "image/webp"]`. No malformed | ||
| `remotePatterns` hostnames. | ||
@@ -73,3 +97,3 @@ 8. **Asset proxy TTLs** are type-keyed (image/css/font) at `365d + SWR 30d`. | ||
| revalidateTag, | ||
| revalidate = 86400, // 24h time-based fallback; pair with tags for surgical invalidation | ||
| revalidate = 2592000, // 30d time-based fallback; pair with tags for surgical invalidation | ||
| mutation = false, | ||
@@ -98,3 +122,3 @@ }: FetchOptions): Promise<T | null> { | ||
| export const WP_CACHE_DURATIONS = { | ||
| pages: 86400, posts: 86400, menus: 86400, staticPages: 604800, default: 86400, | ||
| pages: 2592000, posts: 2592000, menus: 2592000, staticPages: 2592000, default: 2592000, // 30d | ||
| } as const; | ||
@@ -101,0 +125,0 @@ |
@@ -21,5 +21,11 @@ --- | ||
| **Cache by intent (read vs. mutation), never by the HTTP method.** Some reads must use `POST` because | ||
| they take a body (e.g. CCDS `geo-search`). They still have to cache like a `GET`. Mutations must never | ||
| cache. Getting this wrong silently turns a static page into a per-request dynamic render. | ||
| they take a body (e.g. CCDS `geo-search`). Their *data* still has to cache like a `GET` — send | ||
| `next: { revalidate, tags }`, never `no-store`. Mutations must never cache. | ||
| **But caching the data is not the same as making the page static.** A POST read caches its response | ||
| cross-request, yet Next.js still renders the *route* dynamically (`private, no-store`). The data-fetch | ||
| cache (layer 2) and the route's static/dynamic classification (layers 1/5) are **independent** — see | ||
| the two sections below. This is the WEB-1069 gotcha and the reason city/community pages needed an edge | ||
| override even though their fetches were already cached. | ||
| --- | ||
@@ -45,17 +51,41 @@ | ||
| ## How Next.js 16 actually decides (the rule behind the rule) | ||
| ## POST reads: the data caches, but the route stays dynamic (the key gotcha) | ||
| From `next/dist/server/lib/patch-fetch.js`: | ||
| Two independent facts, both true: | ||
| - `fetch` is **not cached by default**. A non-`GET` method is "uncacheable" **only when no explicit | ||
| cache config is provided**. Concretely, a POST is auto-no-cached only when it has no `next`/`cache` | ||
| options *and* the segment `revalidate` is `0`. | ||
| - Therefore **a `POST` IS cached cross-request when you pass an explicit `next: { revalidate }`** (and | ||
| the segment isn't `revalidate: 0`). This is why the WordPress GraphQL POST caches fine. | ||
| - The **request body is part of the cache key** (`incremental-cache` → `generateCacheKey` hashes | ||
| `init.body`), so different filter bodies cache independently and never collide. | ||
| 1. **The data fetch caches.** From `next/dist/server/lib/patch-fetch.js`: a `fetch` is uncacheable | ||
| only when it has no `next`/`cache` options *and* the segment `revalidate` is `0`. So a POST **with** | ||
| `next: { revalidate }` IS cached cross-request — the **request body is part of the cache key** | ||
| (`generateCacheKey` hashes `init.body`), so different filter bodies cache independently. This is | ||
| real and bounds origin/CCDS load; it is why the WordPress GraphQL POST caches fine. | ||
| **Implication:** you do **not** need `unstable_cache` or a proxy header override to cache POST reads. | ||
| Just send `next: { revalidate, tags }`. | ||
| 2. **The route still renders dynamically.** Empirically (WEB-1069, Next 16), a page whose data comes | ||
| from a POST read prerenders as `ƒ` (Dynamic) and serves `cache-control: private, no-store` — **even | ||
| with** `next: { revalidate, tags }` on the fetch. Next treats the POST as request-time data for | ||
| prerendering, so ISR (layer 1) never engages. GET reads do not have this problem — they ISR | ||
| natively (`s-maxage`, `x-nextjs-cache: HIT`). | ||
| **Implication:** `next: { revalidate, tags }` on a POST read is **necessary** (data cache) but **not | ||
| sufficient** to make the page CDN-cacheable. You must additionally pick a rendering/edge strategy | ||
| (next section). `revalidate` + `tags` alone will NOT flip a POST-read page from `private, no-store` to | ||
| `public`. | ||
| ## Making a POST-read page cacheable (rendering / edge layer) | ||
| Pick one (ordered by risk, lowest first): | ||
| 1. **CDN edge override (current, lowest risk).** `src/proxy.ts` matches the city/community paths and | ||
| sets `Cache-Control: public, s-maxage=2592000, stale-while-revalidate=2592000` — the **same** | ||
| header the ISR pages emit (see `expireTime` under ISR tiers), so the CDN policy is uniform across | ||
| static and dynamic routes. The origin stays dynamic; the CDN caches the deterministic-per-URL | ||
| response. Per-repo regex, no build-time risk — this is what WEB-1069 shipped. | ||
| 2. **`export const dynamic = "force-static"` (interim).** Forces the POST read to `force-cache` and | ||
| prerenders the route as real ISR (confirmed via `prerender-manifest.json`). Removed in WEB-1058 | ||
| because a CCDS failure at build time cached a **blank page**; safer now that reads throw on 5xx at | ||
| runtime (a failed revalidation keeps the last good cache), but full prerender is sensitive to | ||
| null/bad records — keep the page fully null-safe and validate per repo before adopting. | ||
| 3. **`cacheComponents: true` + `use cache` (strategic).** Wrap the POST read in `use cache` so its data | ||
| lands in the static shell (PPR). Next-recommended long term; larger migration — do **not** mix ad | ||
| hoc with route-segment `export const revalidate`. | ||
| --- | ||
@@ -81,3 +111,3 @@ | ||
| revalidateTag, | ||
| revalidate = 86400, // 24h time-based fallback; pair with tags for surgical invalidation | ||
| revalidate = 2592000, // 30d time-based fallback; pair with tags for surgical invalidation | ||
| mutation = false, | ||
@@ -122,14 +152,27 @@ }: FetchOptions): Promise<T | null> { | ||
| | Route type | `export const revalidate` | | ||
| |------------|---------------------------| | ||
| | Listing / index | `2592000` (30d) — webhook handles freshness | | ||
| | Detail / city / community (`[slug]`) | `86400` (24h) | | ||
| | State / advisor landing | `604800` (7d) | | ||
| | WP catch-all (`[[...uri]]`) | `604800` (7d) | | ||
| CCDS/WP data changes rarely and is refreshed on-demand via webhooks + tags, so the time-based default | ||
| is intentionally **long** (WEB-1069): | ||
| | Route / config | Value | | ||
| |----------------|-------| | ||
| | CCDS reads (client default) | `2592000` (30d) | | ||
| | WordPress GraphQL reads (`WP_CACHE_DURATIONS`) | `2592000` (30d) | | ||
| | Listing / detail / city / community / state / landing / WP catch-all | `2592000` (30d) | | ||
| | `next.config` `expireTime` (CDN stale window) | `5184000` (60d) → emits `s-maxage=2592000, stale-while-revalidate=2592000` | | ||
| | `next.config` image `minimumCacheTTL` | `31536000` (1y) — optimized images are content-hashed, never change | | ||
| - Every cacheable route exports `revalidate`. Do **not** add it to form/personalized routes. | ||
| - Prefer `export const revalidate` over `dynamic = "force-static"`: a failed ISR revalidation then | ||
| - **`expireTime` sets the CDN stale window.** Next emits `s-maxage=<revalidate>, | ||
| stale-while-revalidate=<expireTime − revalidate>` for ISR pages, so `expireTime` **must be ≥ the | ||
| largest `revalidate`** or the stale window is invalid (the WEB-1069 bug: `expireTime: 86400` under a | ||
| 30d revalidate). Set it to `5184000` (60d) so a 30d page yields the same header the proxy sets on | ||
| dynamic pages: `s-maxage=2592000, stale-while-revalidate=2592000`. | ||
| - The long default is safe because a missed webhook still self-heals within 30d; use `tags` for | ||
| immediate surgical invalidation. Shorter tiers (24h/7d) are fine per-route if a source is volatile. | ||
| - Default to `export const revalidate` over `dynamic = "force-static"`: a failed ISR revalidation then | ||
| preserves the last good cache instead of overwriting it with a broken page. Pair with a client that | ||
| **throws on 5xx at runtime** (so ISR keeps the previous version) but returns an error during the | ||
| build phase (so `generateStaticParams` can skip a bad entry without failing the build). | ||
| build phase (so `generateStaticParams` can skip a bad entry without failing the build). `force-static` | ||
| is still a valid POST-read option (see "Making a POST-read page cacheable") **once** the page is | ||
| fully null-safe and the throw-on-5xx guard is in place. | ||
@@ -153,4 +196,5 @@ --- | ||
| - ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached → the route | ||
| renders dynamically (`private, no-store`). This is the exact regression that broke city pages. | ||
| - ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached (origin hit | ||
| every render). Note: even a *correctly* cached POST read still renders the route dynamically — that | ||
| needs an edge override, not just `next` options (see "Making a POST-read page cacheable"). | ||
| - ❌ Bare `fetch(url, { method: "POST", body })` for a read — refetched from origin every render. | ||
@@ -160,3 +204,5 @@ - ❌ `next: { tags }` with no `revalidate` — holds stale data or doesn't cache at runtime. | ||
| - ❌ Treating `React.cache()` as cross-request caching (it's request-scoped dedup only). | ||
| - ❌ `dynamic = "force-static"` on a page whose revalidation can fail (caches a broken page). | ||
| - ❌ `dynamic = "force-static"` on a page that is **not** fully null-safe or lacks throw-on-5xx — a bad | ||
| CCDS record/response at build time caches a broken/blank page (the WEB-1058 regression). It is a | ||
| valid option only once those guards exist. | ||
| - ❌ Mixing `cacheComponents: true` (`"use cache"`) with route-segment `export const revalidate` — | ||
@@ -163,0 +209,0 @@ that is a separate, deliberate migration; do not introduce it ad hoc. |
411170
1.47%