agentleFS
JoviDeCroockJoviDeCroock/pracht/skills/configure-isg/SKILL.md

configure-isg

Skill106 starsChanged 7 months agoMITView on GitHub

Set up ISG revalidation for the project's adapter: `timeRevalidate`/`webhookRevalidate`, the `/__pracht/revalidate` webhook, Cloudflare and Vercel caching, and local verification. Use for "set up ISG", "configure revalidation", "add a revalidation webhook", "my ISG page never updates".

  • Sends data out

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 0 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All adapters expose POST <base>/__pracht/revalidate (PRACHT_REVALIDATE_ENDPOINT from @pracht/core).

    line 80
  2. Very specific:

    Before enabling cache: true, canonicalize or reject stray query params (the docs describe an uncached-gateway pattern), and note that …

    line 139
  3. Very specific:

    Vercel: no faithful local production runtime — pracht preview points at vercel build/vercel dev.

    line 158
  4. Very specific:

    Never overwrite wrangler.jsonc/wrangler.toml or vercel.json — diff and merge, confirming collisions with AskUserQuestion.

    line 171
  5. Very specific:

    Never propose ISG for personalized responses.

    line 173
  6. Very specific:

    Always pair render: "isg" with an explicit revalidate policy — without one the route silently behaves like SSG.

    line 182

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Pracht Configure ISG

ISG renders at build time and regenerates after a time window or an authenticated webhook (https://pracht.resynapse.dev/docs/rendering#isg--incremental-static-generation, https://pracht.resynapse.dev/docs/adapters). The mechanics differ per adapter, and a route with render: "isg" but no revalidate policy never regenerates — the prerenderer only writes an ISG manifest entry when a policy exists. This skill wires the policy, the webhook, and the adapter-specific cache correctly.

Step 1: Identify adapter and candidate routes

MCP: if pracht dev-mcp is registered, prefer its inspect_*, doctor, and verify tools to the CLI.

pracht inspect routes --json    # render + revalidate per route
pracht inspect build --json     # adapterTarget (requires a prior `pracht build`)

Prerequisite: pracht inspect needs a vite config with the pracht plugin wired up. If unsure which routes deserve ISG at all, run /tune-render-mode first — this skill assumes the mode choice is made.

Step 2: Wire the revalidate policy (manifest router)

// src/routes.ts
import { timeRevalidate, webhookRevalidate } from "@pracht/core";

route("/pricing", () => import("./routes/pricing.tsx"), {
  render: "isg",
  revalidate: [timeRevalidate(3600), webhookRevalidate()],
});
  • timeRevalidate(seconds) — requires a positive integer; anything else throws at manifest evaluation.
  • webhookRevalidate() — no arguments; opts the route into the webhook endpoint.
  • revalidate accepts one policy or an array (RouteRevalidate); the array above means "hourly, or sooner when a webhook names this path".

Pages router: time-based ISG is expressed with two static page exports:

export const RENDER_MODE = "isg";
export const REVALIDATE = 3600;

REVALIDATE must be a positive integer literal number of seconds. Build, doctor, and verify reject a missing or misplaced policy. Pages mode does not accept policies on _app or 404, and fenced Markdown/MDX examples are ignored. It does not support webhook or combined policies; eject with generateRoutesFile from @pracht/vite-plugin/pages-router for those.

For dynamic routes, getStaticPaths() enumerates the prerendered params. Paths it did not enumerate render per-request without a cached copy, and webhooks naming them are skipped on Node/Cloudflare (nothing to refresh).

Step 3: The revalidation webhook

All adapters expose POST <base>/__pracht/revalidate (PRACHT_REVALIDATE_ENDPOINT from @pracht/core). For example, an app with base: "/app/" uses:

curl -X POST https://example.com/app/__pracht/revalidate \
  -H "Authorization: Bearer $PRACHT_REVALIDATE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"paths":["/pricing"]}'

At the default base: "/", omit /app. Keep request-body paths base-free; they identify manifest routes rather than public deployment URLs.

  • Auth: PRACHT_REVALIDATE_TOKEN env var; fails closed with 401 when unset or wrong. Providers that can't send bearer auth may use the x-pracht-revalidate-token header instead.
  • Body: paths array, max 64 entries (else 400). Response reports revalidated / skipped / failed arrays plus a details array naming why each path was skipped (not_a_route, not_isg, not_prerendered, no_webhook_policy) or failed — check details first when a webhook appears to do nothing. scope says how far a refresh reached: instance (Node: that server only), colo (Cloudflare Cache API: that data center only), or global (Netlify, Vercel, Cloudflare routes purged via cache: true). Failed paths keep serving the previous copy. Regeneration is single-flighted per path and never replays the caller's cookies/auth headers.

Step 4: Adapter mechanics

AdapterTime revalidationWebhook revalidation
NodeFile mtime vs window, per server; serves stale, refreshes in backgroundRegenerates the on-disk HTML synchronously on the receiving replica only — serve ISG routes from a single instance
Cloudflare (default)Worker-managed Cache API timestamp, env.ASSETS fallback — per coloOverwrites the Cache API entry in the receiving colo only (scope: "colo"); a colo with no cached copy serves the build snapshot as stale and re-renders, a colo holding a copy keeps it until eviction
Cloudflare (cache: true)Edge-tier Workers Caching in front of the Worker for time-revalidated routesWebhook-only routes keep the worker-managed path; time+webhook routes also get their edge entry purged
VercelBuild Output prerender functions: .prerender-config.json with expiration from the time policy and build HTML as fallback, next to a Node Serverless Function per ISG route (Vercel rejects ISR on an Edge Function)For webhook revalidation, x-vercel-cache verifies the bypass; PRACHT_REVALIDATE_TOKEN becomes the bypassToken and must be set at build time (runtime-only setting → webhook paths report failed until you rebuild). Time-only ISR does not require the token.

Cloudflare specifics (https://pracht.resynapse.dev/docs/adapters/cloudflare#isg-and-workers-caching):

  • The default per-colo path needs no extra config. The cache: true upgrade needs both cloudflareAdapter({ cache: true }) in vite config (optionally { cache: { staleWhileRevalidate: <seconds> } }) and { "cache": { "enabled": true } } in wrangler config.
  • With it on, time-revalidated pages are no longer emitted as build-time snapshots (first request after deploy renders cold); webhook-only routes keep their snapshots.
  • Programmatic purge: purgeCache / routeCacheTag from @pracht/adapter-cloudflare/cache — protect any purge API route with a secret.
  • Cache safety: responses with Set-Cookie, Cache-Control: private/no-store, or Vary: Cookie/Authorization/* are never stored in the shared edge cache.

Step 5: Cache-key cardinality caveats

See https://pracht.resynapse.dev/docs/adapters/cloudflare#isg-and-workers-caching. Node and worker-managed Cloudflare ISG key generated pages by pathname. Workers Caching keys by exact path plus query string (param order and trailing slash included), so /pricing?ref=a and /pricing?ref=b are independent entries with independent revalidation — and attacker-chosen query values create unbounded cold entries. Before enabling cache: true, canonicalize or reject stray query params (the docs describe an uncached-gateway pattern), and note that routes exporting markdown or declaring middleware-owned markdown: true carry Vary: Accept, which multiplies variants per Accept string. Vercel prerender functions are generated with allowQuery: [], so query strings do not fragment that cache. Middleware never runs for cached ISG hits on any adapter — keep per-visitor logic on SSR routes.

Step 6: Verify locally

  • Node: PRACHT_REVALIDATE_TOKEN=dev-secret pracht preview --port 3000 (builds, then runs dist/server/server.js with inherited env; --skip-build reuses a build). Then curl the Step 3 command against localhost:3000 and check the JSON reports your path under revalidated; re-fetch the page and confirm the change. For time policies, use a short window (e.g. timeRevalidate(5)), request after expiry — first response is the stale copy, the next one is fresh.
  • Cloudflare: pracht preview delegates to wrangler dev; put PRACHT_REVALIDATE_TOKEN in .dev.vars so the worker sees it.
  • Vercel: no faithful local production runtime — pracht preview points at vercel build/vercel dev; verify webhook behavior on a real deployment built with PRACHT_REVALIDATE_TOKEN set.

Finish with the standard gate — and run /pre-deploy before shipping:

pracht verify --json
pracht typegen   # if src/routes.ts changed

Rules

  1. Never overwrite wrangler.jsonc/wrangler.toml or vercel.json — diff and merge, confirming collisions with AskUserQuestion.
  2. Never propose ISG for personalized responses. ISG HTML renders always run on a sanitized request (GET, Accept: text/html, path only — no cookies, credentials, query, or body); Cloudflare Workers Caching uses the same isolation with Accept: text/markdown for its markdown cache variant. A loader that reads the session therefore sees an anonymous visitor. On top of that, Set-Cookie or Cache-Control: private/no-store output fails regeneration on every adapter (Vercel answers it with a 503 so its prerender cache keeps the previous page), and Vary: Cookie/Authorization/* is kept out of shared caches by design.
  3. Always pair render: "isg" with an explicit revalidate policy — without one the route silently behaves like SSG.
  4. On Vercel, set PRACHT_REVALIDATE_TOKEN in the build environment, not just at runtime.
  5. Webhook invalidation is per-colo on Cloudflare's default path and per-instance on Node (the response's scope says which); pracht build warns about Cloudflare webhook routes. A webhook-only route never goes stale by time, so pair it with timeRevalidate() to bound the lag, plus cache: true on Cloudflare so the webhook purges every colo.

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.