storyblok-cached-api
v2.0.0
Published
A disk-caching wrapper for Storyblok API in Astro projects to speed up development
Maintainers
Readme
🚀 Storyblok Cached API for Astro
A lightweight, disk-caching wrapper for the Storyblok API in Astro projects that speeds up development by caching API responses to disk.
📦 Installation
pnpm add storyblok-cached-apinpm install storyblok-cached-apiyarn add storyblok-cached-api✨ Features
- 💾 Disk-based caching - Automatically caches API responses during development
- ⚡ Faster development - Load cached stories instead of making repeated API calls
- 🔄 Progressive cache invalidation - Detects changes via Storyblok's
cv(cache version) and only invalidates stories that actually changed - 🎯 Drop-in replacement - Works exactly like the original
useStoryblokApi() - 🔧 Configurable - Control cache directory, verbosity, cv check interval, and enable/disable caching
- 📝 TypeScript strict - Written in strict TypeScript with full type safety
- 🧪 Fully tested - Comprehensive test suite with 100% coverage
🎯 Why?
During development, you often refresh your page multiple times, causing repeated API calls to Storyblok. This library caches those responses to disk, making subsequent requests instant and reducing API quota usage.
📖 Usage
Basic Usage
Simply replace useStoryblokApi() with useCachedStoryblokApi():
// Before
import { useStoryblokApi } from "@storyblok/astro";
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get("cdn/stories/home");// After
import { useCachedStoryblokApi } from "storyblok-cached-api";
const storyblokApi = useCachedStoryblokApi();
const { data } = await storyblokApi.get("cdn/stories/home");That's it! Your API responses are now cached to .sb-dev-cache/ in your project root.
With Custom Options
import { useCachedStoryblokApi } from "storyblok-cached-api";
const storyblokApi = useCachedStoryblokApi({
enableCache: true, // Enable/disable caching (default: NODE_ENV === 'development')
cacheDir: "my-custom-cache", // Custom cache directory (default: '.sb-dev-cache')
verbose: false, // Disable console logs (default: true)
});
const { data } = await storyblokApi.get("cdn/stories/home", {
version: "draft",
});Complete Example in Astro
---
// src/pages/index.astro
import { useCachedStoryblokApi } from "storyblok-cached-api";
const storyblokApi = useCachedStoryblokApi();
// First request hits the API and caches the response
const { data } = await storyblokApi.get("cdn/stories/home", {
version: "draft",
});
// Subsequent requests load from cache instantly
const story = data.story;
---
<html>
<head>
<title>{story.name}</title>
</head>
<body>
<h1>{story.content.title}</h1>
</body>
</html>🔧 API Reference
useCachedStoryblokApi(options?)
Returns a Storyblok API instance with progressive caching enabled.
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enableCache | boolean | process.env.NODE_ENV === 'development' | Enable or disable caching |
| cacheDir | string | '.sb-dev-cache' | Directory path for cache storage |
| verbose | boolean | true | Enable console logging for cache operations |
| cvCheckIntervalMs | number | 5000 | Interval (ms) between cache version checks against Storyblok. Set to 0 to check on every request |
| version | "draft" | "published" | 'published' | Change detection mode. "published" uses cv + published_at_gt. "draft" uses updated_at timestamps to also detect draft saves |
generateCacheKey(path, params?)
Generates a unique cache key based on the path and parameters.
import { generateCacheKey } from "storyblok-cached-api";
const key = generateCacheKey("cdn/stories/home", { version: "draft" });
// Returns: "cdn_stories_home__a1b2c3d4e5f6g7h8.json"readFromCache(cacheKey, cacheDir?, verbose?)
Reads data from the disk cache.
import { readFromCache } from "storyblok-cached-api";
const data = readFromCache("cdn_stories_home__hash.json");writeToCache(cacheKey, data, cacheDir?, verbose?)
Writes data to the disk cache.
import { writeToCache } from "storyblok-cached-api";
writeToCache("cdn_stories_home__hash.json", { story: { name: "Home" } });clearCache(cacheDir?)
Clears all cache files from the cache directory.
import { clearCache } from "storyblok-cached-api";
const filesDeleted = clearCache(); // Deletes all .json files in .sb-dev-cache/
console.log(`Deleted ${filesDeleted} cache files`);🧹 Clearing the Cache
To clear the cache, simply delete the cache directory:
# Default cache directory
rm -rf .sb-dev-cacheOr use the clearCache() function:
import { clearCache } from "storyblok-cached-api";
clearCache(); // Clears default .sb-dev-cache/
clearCache("my-custom-cache"); // Clears custom directory🎛️ Environment-based Behavior
By default, caching is only enabled in development:
// Caching enabled when NODE_ENV === 'development'
const api = useCachedStoryblokApi();
// Force enable/disable regardless of environment
const apiAlwaysCached = useCachedStoryblokApi({ enableCache: true });
const apiNeverCached = useCachedStoryblokApi({ enableCache: false });📂 Cache Directory
The cache directory (.sb-dev-cache/ by default) should be added to your .gitignore:
# .gitignore
.sb-dev-cache/🔄 How It Works
Progressive Cache Invalidation
The library uses a two-tier strategy to keep the cache fresh without rebuilding everything.
Published mode (default)
- Space-level check (lightweight): On each request (throttled by
cvCheckIntervalMs), the library callscdn/spaces/meto get Storyblok'scv(cache version) — a Unix timestamp that changes whenever any content is published. - If
cvis unchanged → all cache is valid, zero re-fetches needed. - If
cvchanged → the library queriescdn/stories?published_at_gt=<lastCheckDate>to find only the stories that actually changed, then invalidates just those cache files.
Draft mode (version: 'draft')
Storyblok's cv only changes on publish, so draft saves would go undetected in published mode. When you set version: 'draft':
- On each check (throttled), the library fetches stories sorted by
updated_at:descwithversion=draft. - It compares each story's
updated_attimestamp against the last check time. - Stories updated since the last check are invalidated — including unpublished draft saves.
// Recommended for development with Storyblok's visual editor
const api = useCachedStoryblokApi({
version: 'draft',
cvCheckIntervalMs: 10000,
});Non-story endpoints
Endpoints like cdn/links and cdn/datasources are always invalidated when changes are detected, since granular change detection is not available for them.
Basic Flow
- When you call
storyblokApi.get(slug, params), the library generates a unique cache key based on the slug and parameters - It checks the space's cache version (throttled) to detect changes
- If a cached file exists and is still valid, it loads the data from disk (instant)
- If no cache exists or it was invalidated, it queries the Storyblok API and saves the response to disk
- Next time you request the same slug/params, it loads from cache
🧪 Testing
The library comes with a comprehensive test suite:
# Run tests
pnpm test
# Run tests with UI
pnpm test:ui
# Run tests with coverage
pnpm test:coverage🛠️ Development
# Install dependencies
pnpm install
# Build the library
pnpm build
# Run tests
pnpm test
# Run type checking
pnpm typecheck
# Lint
pnpm lint📋 Requirements
- Node.js >= 18.0.0
@storyblok/astro>= 7.0.0 (peer dependency)
🤝 Peer Dependencies
This library requires @storyblok/astro to be installed in your project:
pnpm add @storyblok/astroThe library uses peerDependencies to ensure you're using your own version of Storyblok, avoiding version conflicts and duplicate installations.
📝 License
MIT
🙏 Contributing
Contributions are welcome! Please open an issue or submit a pull request.
🐛 Issues
Found a bug? Please open an issue.
Made with ❤️ for faster Astro + Storyblok development
