Skip to content

Caching – astrojs-wp-integration

This package integrates with Astro route caching through live-loader cacheHint values and one invalidation action:

  • createWpCacheInvalidateAction() for posts, terms, and users

Astro owns the cache runtime and configuration. Use the Astro docs as the source of truth for setup and provider behavior:

  • Live loaders return cacheHint.lastModified when the WordPress payload exposes a trustworthy timestamp.
  • Live loaders return tags for entry identity and useful relationships such as authors and terms.
  • The cache invalidation action fetches the latest WordPress entity, derives the entry tag, and calls context.cache.invalidate() for you.
flowchart LR
A[App route or API endpoint] --> B[getLiveEntry() or getLiveCollection()]
B --> C[wp-astrojs-integration live loader]
C --> D[WordPressClient]
D --> E[WordPress REST API]
C --> F[cacheHint with tags and lastModified]
F --> A
A --> G[Route applies Astro.cache.set(...) or context.cache.set(...)]
H[createWpCacheInvalidateAction] --> I[Read changed WordPress entity]
I --> J[Derive narrow entry tag]
J --> K[context.cache.invalidate(...)]

The invalidation action is intentionally narrower than the full live-loader tag set:

  • post invalidation targets only the changed entry tag
  • term invalidation targets only the changed entry tag
  • user invalidation targets only the changed entry tag

That keeps cached listing strategies effective when only one changed item needs to be refreshed.

Pass the live-loader cache hint straight into Astro.cache.set() and then add your route freshness settings.

src/pages/books/[slug].astro
---
import { getLiveEntry } from 'astro:content';
const { entry, error, cacheHint } = await getLiveEntry('books', { slug: Astro.params.slug });
if (error || !entry) {
return Astro.redirect('/404');
}
if (cacheHint) {
Astro.cache.set(cacheHint);
}
Astro.cache.set({ maxAge: 300 });
---
<h1>{entry.data.title.rendered}</h1>

For public/shared AI endpoints, fluent-wp-client/ai-sdk tool factories can use a fetch override that delegates reads to a live collection. This keeps AI tooling owned by fluent-wp-client while still letting the Astro endpoint consume live-loader cache hints.

src/pages/api/ai/post.json.ts
import { getLiveEntry } from 'astro:content';
import type { APIRoute } from 'astro';
import { getContentTool } from 'fluent-wp-client/ai-sdk';
import { wp } from '../../lib/wp';
export const GET: APIRoute = async (context) => {
const tool = getContentTool(wp, {
contentType: 'posts',
fetch: async (input) => {
if (!input.id && !input.slug) {
throw new Error('Provide either id or slug to read a post.');
}
const lookup = input.id ? { id: input.id } : { slug: input.slug };
const { entry, error, cacheHint } = await getLiveEntry('posts', lookup);
if (error) throw error;
if (cacheHint) context.cache.set(cacheHint);
return { item: entry?.data };
},
});
if (!tool.execute) throw new Error('Content tool is not executable.');
const result = await tool.execute({ slug: 'hello-world' }, {
toolCallId: 'read-post',
messages: [],
});
context.cache.set({ maxAge: 300, swr: 60 });
return Response.json(result);
};

Do not use this pattern for per-user reads. Live collections do not receive request, cookies, or locals; use a request-scoped WordPressClient and context.cache.set(false) for user-specific content.

Filtered live collections can use collection-level cache hints when the result set has stable membership, such as include, exclude, taxonomy filters, author filters, or explicit ordering.

Free-text search filters do not receive a collection-level cache hint. A search result can change when any matching or formerly matching WordPress entity changes, so caching the whole result set under one route hint is not safe. Entries returned from the search still include per-entry cache hints, so routes can cache individual entity pages or compose their own narrower strategy.

src/pages/search.astro
---
import { getLiveCollection } from 'astro:content';
const { entries, cacheHint } = await getLiveCollection('posts', {
search: Astro.url.searchParams.get('q') ?? '',
});
// `cacheHint` is undefined for free-text search results.
if (cacheHint) {
Astro.cache.set(cacheHint);
}
---
<ul>
{entries.map((entry) => <li>{entry.data.title.rendered}</li>)}
</ul>

Create the action next to your existing WordPress write actions:

src/actions/index.ts
import {
createWpCacheInvalidateAction,
} from 'wp-astrojs-integration';
import { wordPressAuthBridge } from '../lib/auth/bridge';
export const server = {
wpCacheInvalidate: createWpCacheInvalidateAction(wordPressAuthBridge.getClient),
};

Call it with id and entity. Add post_type for posts, taxonomy for terms, and nothing extra for users:

src/actions/cache-inputs.ts
import { z } from 'zod';
const baseSchema = z.object({
id: z.number(),
}).passthrough();
export const postSchema = baseSchema.extend({
entity: z.literal('post'),
post_type: z.string(),
});
export const taxonomySchema = baseSchema.extend({
entity: z.literal('term'),
taxonomy: z.string(),
});
export const userSchema = baseSchema.extend({
entity: z.literal('user'),
});

Examples:

await actions.wpCacheInvalidate({
id: 42,
entity: 'post',
post_type: 'book',
});
await actions.wpCacheInvalidate({
id: 8,
entity: 'term',
taxonomy: 'genre',
});
await actions.wpCacheInvalidate({
id: 3,
entity: 'user',
});

For user-specific pages, use a request-scoped fluent-wp-client instance from middleware or the auth bridge and opt out per route instead of caching everything globally:

That is the right default for dashboards, account pages, or any route where request cookies or user identity affect the HTML. Do not route per-user reads through live loaders; live loaders do not receive request, cookies, or locals.

  • TypeScript88%
  • PHP11%
  • Astro1%