Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/storyblok-cache-version-refresh-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@graphcommerce/storyblok-ui': patch
---

Add `requestStoryblokCacheVersionRefresh()` and stop the cache-version refresh from logging a spurious "apiPlugin not loaded" warning.

On-demand revalidation (e.g. a cache-notify webhook) previously had to call `refreshStoryblokCacheVersion()`, which calls `getStoryblokApi()`. In a context where `storyblokInit` has not run — such as a serverless API route that never rendered a page — that logs "You can't use getStoryblokApi if you're not loading apiPlugin." The new `requestStoryblokCacheVersionRefresh()` only flips a flag that the next published read consumes, so it never touches the Storyblok client and never logs the warning. In a shared-process deployment (Kubernetes) the page regeneration that follows picks it up immediately; on serverless, freshness falls back to the `storyblok.cacheVersionTtl` interval.

`refreshStoryblokCacheVersion()` is removed — replace any direct calls with `requestStoryblokCacheVersionRefresh()`.
28 changes: 25 additions & 3 deletions packages/storyblok-ui/lib/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,40 @@ const MAX_RETRIES = 3
*/
const CV_REFRESH_TTL_MS = (storyblok?.cacheVersionTtl ?? 60) * 1000
let cvRefreshedAt = 0
let forceRefreshRequested = false

export async function refreshStoryblokCacheVersion(force = false): Promise<void> {
/**
* Request an immediate cache-version refresh on the next published read.
*
* Safe to call from any context — including a cache-notify webhook handler that runs before
* `storyblokInit` — because it only flips a flag and never touches the Storyblok client (so it
* never logs the "apiPlugin not loaded" warning). On a shared-process deployment (e.g. Kubernetes)
* the page regeneration that follows runs in the same process and picks this up, so just-published
* content is fetched immediately. On serverless the flag is process-local, so freshness there
* falls back to the TTL.
*/
export function requestStoryblokCacheVersionRefresh(): void {
forceRefreshRequested = true
}

/**
* Advance the pinned cache-version when forced (see {@link requestStoryblokCacheVersionRefresh}) or
* once the TTL has elapsed. Only called from the published-read fetch functions below, where
* `storyblokInit` has always run, so `getStoryblokApi()` is safe here.
*/
async function refreshStoryblokCacheVersion(): Promise<void> {
if (isDev) return
const now = Date.now()
if (!force && now - cvRefreshedAt < CV_REFRESH_TTL_MS) return
if (!forceRefreshRequested && now - cvRefreshedAt < CV_REFRESH_TTL_MS) return
// Optimistically mark refreshed so concurrent callers don't stampede cdn/spaces/me.
forceRefreshRequested = false
cvRefreshedAt = now
try {
await getStoryblokApi().get('cdn/spaces/me')
} catch {
// A failed refresh keeps the previous cv; reset so the next call retries.
// A failed refresh keeps the previous cv; force a retry on the next read.
cvRefreshedAt = 0
forceRefreshRequested = true
}
}

Expand Down
Loading