npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

storyblok-cached-api

v2.0.0

Published

A disk-caching wrapper for Storyblok API in Astro projects to speed up development

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-api
npm install storyblok-cached-api
yarn 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-cache

Or 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)

  1. Space-level check (lightweight): On each request (throttled by cvCheckIntervalMs), the library calls cdn/spaces/me to get Storyblok's cv (cache version) — a Unix timestamp that changes whenever any content is published.
  2. If cv is unchanged → all cache is valid, zero re-fetches needed.
  3. If cv changed → the library queries cdn/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':

  1. On each check (throttled), the library fetches stories sorted by updated_at:desc with version=draft.
  2. It compares each story's updated_at timestamp against the last check time.
  3. 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

  1. When you call storyblokApi.get(slug, params), the library generates a unique cache key based on the slug and parameters
  2. It checks the space's cache version (throttled) to detect changes
  3. If a cached file exists and is still valid, it loads the data from disk (instant)
  4. If no cache exists or it was invalidated, it queries the Storyblok API and saves the response to disk
  5. 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/astro

The 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