From d78945221d68ceab073f93572d87f12be0c72d47 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 19 Feb 2026 09:21:40 -0500 Subject: [PATCH] fix(content): clear route cache on content changes so slug pages reflect updated data (#15573) * fix(content): clear route cache on content changes so slug pages reflect updated data * Add changeset * test: cover HMR updates for content slug routes --------- Co-authored-by: astrobot-houston --- .changeset/clear-route-cache-content.md | 5 ++++ .../vite-plugin-content-virtual-mod.ts | 3 +++ .../src/core/app/entrypoints/virtual/dev.ts | 7 ++++++ packages/astro/src/vite-plugin-app/app.ts | 8 +++++++ .../vite-plugin-app/createAstroServerApp.ts | 7 ++++++ .../hmr-markdown/src/pages/blog/[slug].astro | 24 +++++++++++++++++++ packages/astro/test/hmr-markdown.test.js | 10 ++++++++ 7 files changed, 64 insertions(+) create mode 100644 .changeset/clear-route-cache-content.md create mode 100644 packages/astro/test/fixtures/hmr-markdown/src/pages/blog/[slug].astro diff --git a/.changeset/clear-route-cache-content.md b/.changeset/clear-route-cache-content.md new file mode 100644 index 000000000000..7e84854b56de --- /dev/null +++ b/.changeset/clear-route-cache-content.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Clear the route cache on content changes so slug pages reflect updated data during dev. diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index f2a67f35b5e9..a70214378964 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -39,6 +39,9 @@ function invalidateDataStore(viteServer: ViteDevServer) { // Pass `true` to mark this as HMR invalidation so Vite drops cached SSR results. environment.moduleGraph.invalidateModule(module, undefined, timestamp, true); } + // Signal the SSR runner to clear its route cache so that getStaticPaths() + // is re-evaluated with the updated content collection data. + environment.hot.send('astro:content-changed', {}); viteServer.environments.client.hot.send({ type: 'full-reload', path: '*', diff --git a/packages/astro/src/core/app/entrypoints/virtual/dev.ts b/packages/astro/src/core/app/entrypoints/virtual/dev.ts index 31eafcc72822..01fdf63f8010 100644 --- a/packages/astro/src/core/app/entrypoints/virtual/dev.ts +++ b/packages/astro/src/core/app/entrypoints/virtual/dev.ts @@ -26,6 +26,13 @@ export const createApp: CreateApp = ({ streaming } = {}) => { logger.error('router', `Failed to update routes via HMR:\n ${e}`); } }); + + // Listen for content collection changes via HMR. + // Clear the route cache so getStaticPaths() is re-evaluated with fresh data. + import.meta.hot.on('astro:content-changed', () => { + if (!currentDevApp) return; + currentDevApp.pipeline.routeCache.clearAll(); + }); } return currentDevApp; diff --git a/packages/astro/src/vite-plugin-app/app.ts b/packages/astro/src/vite-plugin-app/app.ts index bb2258ddcd4a..a50c6966f23c 100644 --- a/packages/astro/src/vite-plugin-app/app.ts +++ b/packages/astro/src/vite-plugin-app/app.ts @@ -61,6 +61,14 @@ export class AstroServerApp extends BaseApp { ensure404Route(this.manifestData); } + /** + * Clears the route cache so that getStaticPaths() is re-evaluated. + * Called via HMR when content collection data changes. + */ + clearRouteCache(): void { + this.pipeline.clearRouteCache(); + } + async devMatch(pathname: string): Promise { const matchedRoute = await matchRoute( pathname, diff --git a/packages/astro/src/vite-plugin-app/createAstroServerApp.ts b/packages/astro/src/vite-plugin-app/createAstroServerApp.ts index 32bd70f77d86..03127f2625a4 100644 --- a/packages/astro/src/vite-plugin-app/createAstroServerApp.ts +++ b/packages/astro/src/vite-plugin-app/createAstroServerApp.ts @@ -71,6 +71,13 @@ export default async function createAstroServerApp( actualLogger.error('router', `Failed to update routes via HMR:\n ${e}`); } }); + + // Listen for content collection changes via HMR. + // Clear the route cache so getStaticPaths() is re-evaluated with fresh data. + import.meta.hot.on('astro:content-changed', () => { + app.clearRouteCache(); + actualLogger.debug('router', 'Route cache cleared due to content change'); + }); } return { diff --git a/packages/astro/test/fixtures/hmr-markdown/src/pages/blog/[slug].astro b/packages/astro/test/fixtures/hmr-markdown/src/pages/blog/[slug].astro new file mode 100644 index 000000000000..8584d88cacd7 --- /dev/null +++ b/packages/astro/test/fixtures/hmr-markdown/src/pages/blog/[slug].astro @@ -0,0 +1,24 @@ +--- +import { getCollection, render } from 'astro:content'; + +export async function getStaticPaths() { + const posts = await getCollection('blog'); + return posts.map((post) => ({ params: { slug: post.id }, props: { post } })); +} + +const { post } = Astro.props; + +const { Content } = await render(post); +--- + + + + {post.data.title} + + +

{post.data.title}

+
+ +
+ + diff --git a/packages/astro/test/hmr-markdown.test.js b/packages/astro/test/hmr-markdown.test.js index 93257fb37154..62760295ac7f 100644 --- a/packages/astro/test/hmr-markdown.test.js +++ b/packages/astro/test/hmr-markdown.test.js @@ -32,6 +32,11 @@ describe('HMR: Markdown updates', () => { let html = await response.text(); assert.ok(html.includes('Original content')); + response = await fixture.fetch('/blog/post'); + assert.equal(response.status, 200); + html = await response.text(); + assert.ok(html.includes('Original content')); + await fixture.editFile(markdownPath, UPDATED_CONTENT); await fixture.onNextDataStoreChange(); @@ -39,6 +44,11 @@ describe('HMR: Markdown updates', () => { assert.equal(response.status, 200); html = await response.text(); assert.ok(html.includes('Updated content')); + + response = await fixture.fetch('/blog/post'); + assert.equal(response.status, 200); + html = await response.text(); + assert.ok(html.includes('Updated content')); }, ); });