Acceptance Criteria
Astro
- The Vite plugin registered by the Astro integration (WORK-240) gains a
configureServer hook that watches site.contentDir for .md changes - On change/add/unlink: invalidates the loader's cached site (via the WORK-244-style shared loader's
invalidateSite()) and triggers a Vite full-reload via server.ws.send({ type: 'full-reload' }) - Sandbox examples directory (
site.sandbox?.examplesDir) is watched too if configured, matching the SvelteKit setupContentHmr shape line-for-line - Editing a
.md file in the content dir while astro dev is running causes the browser to reload and display the updated content within ~1s
Nuxt
- Same hook pattern as Astro, registered via the Nuxt module's Vite plugin
- Same validation: editing
.md in nuxt dev triggers reload and updated content within ~1s
Eleventy
- The
refraktPlugin (packages/eleventy/src/plugin.ts) gains a setupContentWatcher(eleventyConfig, contentDir, examplesDir?) helper that calls eleventyConfig.addWatchTarget(contentDir) and triggers a full rebuild on changes - Editing a
.md file while npx @11ty/eleventy --serve is running triggers an Eleventy rebuild + browser reload within ~2s (Eleventy's rebuild is slower than Vite HMR; documented as expected) - Eleventy template integration's
eleventy.config.js is updated to call the new helper
Shared
packages/sveltekit/src/content-hmr.ts:setupContentHmr is extracted into @refrakt-md/transform/node (or a new @refrakt-md/content/dev entry point) so all Vite-based adapters share the same watcher logicpackages/sveltekit/src/plugin.ts switches to import from the shared location, removing the local copy- The shared helper takes a generic invalidation callback so adapters that don't use
createRefraktLoader (or use it differently) can plug their own cache-busting in
Approach
Vite-based adapters (Astro, Nuxt): extract setupContentHmr whole. The function is already framework-agnostic — it works against Vite's ViteDevServer shape. The Astro integration's Vite plugin registers a configureServer hook that calls the extracted helper. Same for Nuxt.
// packages/transform/src/dev/content-hmr.ts (or @refrakt-md/content/dev)
export function setupContentHmr(
server: ViteDevServer,
contentDir: string,
examplesDir: string | undefined,
onInvalidate: () => void,
): void { /* lifted from sveltekit/src/content-hmr.ts */ }
Adapters wire it in:
// Astro Vite plugin
configureServer(server) {
setupContentHmr(server, contentDir, examplesDir, () => loader.invalidateSite());
}
Eleventy: the --serve mode uses Eleventy's own chokidar-backed watcher rather than Vite. eleventyConfig.addWatchTarget(path) registers a directory; Eleventy then triggers a full rebuild on changes. The cross-page pipeline runs each rebuild anyway because Eleventy re-imports the data file, so no manual cache invalidation is needed — the data file's createRefraktLoader instance is re-created from scratch.
// packages/eleventy/src/plugin.ts
export function refraktPlugin(eleventyConfig: any, options: RefraktEleventyOptions = {}): void {
// existing logic ...
if (options.contentDir) {
eleventyConfig.addWatchTarget(options.contentDir);
}
if (options.examplesDir) {
eleventyConfig.addWatchTarget(options.examplesDir);
}
}
Eleventy's rebuild latency is inherently higher than Vite HMR — accept that, document the latency as expected (this is Eleventy's design, not a bug to fix).
Next.js excluded: Next's dev server triggers re-evaluation when an imported file changes. For .md content files to participate, the loader must import them in a way Webpack/Turbopack tracks. That's already how the Next template works (generateStaticParams calls the loader at module evaluation time, and Next's dev server re-runs that on any deps change). No custom watcher needed; just document the behaviour.
Dependencies
- WORK-240 — Astro Vite plugin (extends with
configureServer) - WORK-241 — Nuxt Vite plugin (extends with
configureServer) - WORK-244 — Astro template uses
createRefraktLoader (needed for invalidateSite() to work; coordinated sibling)
References
- SPEC-058 — adapter parity spec (this item moves Content HMR out of "Out of scope")
- SPEC-030 — original deferral
packages/sveltekit/src/content-hmr.ts:14–53 — current SvelteKit implementation to extract
Resolution
Completed: 2026-05-21
Branch: `claude/update-adapters-5CJgQ`
What was done
- Shared `setupContentHmr` — moved `packages/sveltekit/src/content-hmr.ts` into `@refrakt-md/transform/node`. The helper uses a new structurally-typed `MinimalViteDevServer` interface so it doesn't pull `vite` into `@refrakt-md/transform`'s deps.
- SvelteKit plugin imports from the shared location; the local copy is deleted.
- Astro integration registers a new `refrakt-md:content-hmr` Vite plugin whose `configureServer` calls `setupContentHmr(server, contentDir, examplesDir)`. Editing a `.md` file under the content dir during `astro dev` triggers a full browser reload via Vite's WS `full-reload` message.
- Nuxt module registers the same Vite plugin alongside the site-tokens + runes plugins. The existing `builder:watch` hook stays for Nuxt-side regeneration; the Vite plugin handles the browser reload.
- Eleventy `refraktPlugin` gains `contentDir` + `examplesDir` options that register Eleventy watch targets via `addWatchTarget`. Template's `eleventy.config.js` passes `contentDir: resolve('content')`. `--serve` mode rebuilds on `.md` edits.
Notes
The shared `setupContentHmr` already accepted an optional `onInvalidate` callback (it was added when the SvelteKit plugin moved to module-cached loader). Astro and Nuxt don't pass an invalidation callback today because the user's loader instance lives in their own setup module — not accessible from the integration. The integration's own loader (used inside the runes-Vite-plugin callback) is module-scoped and re-evaluated per build, so this is fine. Users who want runtime freshness should pass `dev: true` to their `createRefraktLoader` (documented in the adapter docs).
The criterion mentioned a `setupContentWatcher` helper for Eleventy — implemented inline in `refraktPlugin` since the function body is trivial (`addWatchTarget` plus an existence check). Exposing a separate helper would just add ceremony.
Next.js + HTML excluded per the spec — Next's dev server already handles `.md` import-graph invalidation, and the HTML adapter has no dev server.
Full workspace build clean; all 2652 tests pass; site builds clean with 3 `data-tint=nord` rules.