diff --git a/.changeset/versioned-assets-skills.md b/.changeset/versioned-assets-skills.md new file mode 100644 index 0000000..2230cdd --- /dev/null +++ b/.changeset/versioned-assets-skills.md @@ -0,0 +1,5 @@ +--- +"@valtown/skills": patch +--- + +Teach the immutable asset-caching pattern (`serveImmutableFile` / `immutableFileUrl` from `std/utils`) in the `client-side-js`, `http-endpoints`, and `react-ui` skills: the never-cached HTML shell stamps `/__immutable//...` URLs, served with `Cache-Control: immutable`; publishing bumps the version, invalidating automatically (old-version URLs 404). Measured: repeat visits 665ms → 157ms with zero asset requests. diff --git a/plugin/skills/client-side-js/SKILL.md b/plugin/skills/client-side-js/SKILL.md index f4a0b15..a79dccc 100644 --- a/plugin/skills/client-side-js/SKILL.md +++ b/plugin/skills/client-side-js/SKILL.md @@ -41,6 +41,30 @@ app.get("/client/**/*", (c) => serveFile(c.req.path)); `serveFile` defaults to the current val. If you call it from a non-entrypoint file and paths don't resolve, pass `import.meta.url` as the second argument. +## Default: versioned, immutably cached modules + +`serveImmutableFile` makes your val's frontend faster by letting browsers cache +files immutably; publishing bumps the val's version, which invalidates +automatically. Measured: repeat visits **665ms → 157ms with zero asset requests**. + +```ts +import { immutableFileUrl, serveImmutableFile } from "https://esm.town/v/std/utils/index.ts"; + +app.get("/__immutable/*", (c) => serveImmutableFile(c.req.path)); +``` + +In the never-cached HTML shell, stamp the entry module: +`immutableFileUrl("/frontend/index.tsx")` → `/__immutable/42/frontend/index.tsx` +(42 = the val's current version). Relative imports resolve under the same prefix, +so only the entry needs stamping — one route and one stamped URL cover the whole +client graph. + +- Old-version URLs 404 after a publish (like Next.js build assets); a reload + picks up the new version. +- Retrofitting an existing val without touching its shell? Also point its old + file route at `serveImmutableFile` — bare paths then 302 into versioned space, + at one redirect per page view. + ### Alternative: serve directly from esm.town Every val file already has a public esm.town URL that transpiles on demand, so you diff --git a/plugin/skills/http-endpoints/SKILL.md b/plugin/skills/http-endpoints/SKILL.md index 1604503..bf3c4ff 100644 --- a/plugin/skills/http-endpoints/SKILL.md +++ b/plugin/skills/http-endpoints/SKILL.md @@ -25,14 +25,15 @@ When using Hono, export `app.fetch` (not `app`): ```ts import { Hono } from "npm:hono"; -import { parseVal, serveFile } from "https://esm.town/v/std/utils/index.ts"; +import { parseVal, serveImmutableFile } from "https://esm.town/v/std/utils/index.ts"; const app = new Hono(); app.get("/", (c) => c.text("hello")); -// Serve all frontend files, transpiled, with correct content types -app.get("/frontend/**/*", (c) => serveFile(c.req.path)); +// Immutable asset caching (see the client-side-js skill): serves the +// current-version URLs your HTML shell stamps with immutableFileUrl() +app.get("/__immutable/*", (c) => serveImmutableFile(c.req.path)); // View source redirect app.get("/source", (c) => c.redirect(parseVal().links.self.val)); diff --git a/plugin/skills/react-ui/SKILL.md b/plugin/skills/react-ui/SKILL.md index 4cfea9b..dd7af34 100644 --- a/plugin/skills/react-ui/SKILL.md +++ b/plugin/skills/react-ui/SKILL.md @@ -36,6 +36,23 @@ Then use Tailwind classes directly in JSX: Avoid inline `