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

@invisiblecities/sanity-edge-fetcher

v1.0.6

Published

Lightweight, Edge Runtime-compatible Sanity client for Next.js and Vercel Edge Functions

Readme

Sanity Edge Fetcher

A lightweight, Edge Runtime-compatible Sanity client for Next.js and Vercel Edge Functions.

Why Use This Instead of @sanity/client or next-sanity?

The official Sanity clients (@sanity/client and next-sanity's sanityFetch) have several limitations on Vercel's Edge Runtime:

  • Bundle Size: Official client adds ~50KB to your bundle, this adds only ~3KB (core) or ~7KB (with full caching and stega)
  • Hidden Node.js Dependencies: sanityFetch appears edge-compatible but actually smuggles Node.js-specific code that can cause runtime failures on Vercel Edge Functions
  • Forced Dynamic Rendering: Using the official client often forces pages into dynamic rendering mode, breaking static generation
  • No True Edge Support: Despite claims, the official client isn't truly edge-compatible and relies on polyfills that increase bundle size and reduce performance

Bundle Size Comparison

| Package | Size | Gzipped | Runtime | |---------|------|---------|---------| | @sanity/client | ~150KB | ~50KB | ~50KB | | next-sanity (sanityFetch) | ~160KB | ~52KB | ~52KB | | edge-fetcher (core) | 8.5KB | 2.8KB | ~3KB | | edge-fetcher (with cache) | 16KB | 5.4KB | ~6KB | | edge-fetcher (full) | 27KB | 9.1KB | ~7.3KB |

Result: 87% smaller than official clients, with better edge compatibility.

Features

  • True Edge Runtime compatible - No Node.js dependencies, no polyfills, no hidden incompatibilities
  • Tiny bundle size - ~3KB core, ~7KB with full features (85% smaller than official)
  • Visual editing support - Stega encoding for Sanity Studio presentation mode (v1.0.2+)
  • Vercel-optimized - Works perfectly with Vercel Edge Functions, Middleware, and Edge Config
  • Static generation compatible - No forced dynamic rendering, preserves ISR and SSG
  • TypeScript first - Full type safety with generics
  • Built-in rate limiting - Prevents 429 errors
  • Multi-layer caching - Memory, Redis (Vercel KV/Upstash), and Next.js cache
  • Optional enhancements - Retry, real-time updates via SSE/WebSockets

Installation

# Core functionality only
npm install # (already in your project)

# For retry support (optional)
npm install p-retry

Quick Start

Using Pre-configured Fetchers (Recommended)

import { fetchers } from '@/lib/sanity/edge-fetcher';

// Use pre-configured fetchers for common cases
const posts = await fetchers.cached<Post[]>('*[_type == "post"][0..10]');
const settings = await fetchers.static<Settings>('*[_type == "siteSettings"][0]');
const preview = await fetchers.authenticated<Post>('*[_type == "post"][0]');

// Page data with caching
const page = await fetchers.page<PageData>(
  '*[_type == "page" && slug.current == $slug][0]',
  { slug: 'about' }
);

Direct Usage

import { edgeSanityFetch } from '@/lib/sanity/edge-fetcher';

// Basic query
const posts = await edgeSanityFetch<Post[]>({
  dataset: 'production',
  query: '*[_type == "post"][0..10]',
  useCdn: true
});

// With parameters
const post = await edgeSanityFetch<Post>({
  dataset: 'production',
  query: '*[_type == "post" && slug.current == $slug][0]',
  params: { slug: 'my-post' }
});

Enhanced Features

Multi-Layer Caching

The edge-fetcher includes a sophisticated multi-layer caching system:

import { cachedSanityFetch } from '@/lib/sanity/edge-fetcher';

// Fetch with automatic caching
const posts = await cachedSanityFetch<Post[]>({
  dataset: 'production',
  query: '*[_type == "post"][0..10]',
  cache: {
    ttl: 300,        // 5 minutes
    useRedis: true,  // Use Upstash if configured
    useNextCache: true // Use Next.js cache
  }
});

// Create a cached fetcher with defaults
const fetcher = createCachedFetcher('production', {
  ttl: 60,
  prefix: 'blog:'
});

// Check cache status
const status = getCacheStatus();
console.log('Cache layers:', status);
// { memory: { available: true, size: 5 }, 
//   redis: { available: true, configured: true },
//   nextCache: { available: true } }

// Warm cache on startup
await warmSanityCache([
  { dataset: 'production', query: '*[_type == "post"][0..10]', ttl: 3600 },
  { dataset: 'production', query: '*[_type == "author"]', ttl: 7200 }
]);

// Clear cache when content updates
await clearSanityCache({ dataset: 'production' });

Cache Layers (in order):

  1. In-memory LRU (~1ms) - Ultra-fast, limited size
  2. Upstash Redis (~10-30ms) - Distributed, persistent
  3. Next.js Cache - ISR and static generation

Required Environment Variables for Redis:

# Option 1: Vercel KV (Upstash)
KV_REST_API_URL=https://your-instance.upstash.io
KV_REST_API_TOKEN=your-token
KV_REST_API_READ_ONLY_TOKEN=your-read-token # Optional

# Option 2: Direct Upstash
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token

Automatic Retry

If p-retry is installed, use the enhanced fetcher:

import { edgeSanityFetchWithRetry } from '@/lib/sanity/edge-fetcher';

const posts = await edgeSanityFetchWithRetry<Post[]>(
  {
    dataset: 'production',
    query: '*[_type == "post"]'
  },
  {
    retries: 3,
    minTimeout: 100,
    maxTimeout: 2000
  }
);

Cached Fetcher

Leverage Next.js caching:

import { createCachedSanityFetcher } from '@/lib/sanity/edge-fetcher';

const fetcher = createCachedSanityFetcher('production', 60); // 60s cache
const posts = await fetcher<Post[]>('*[_type == "post"]');

Batch Fetching

Fetch multiple queries in parallel:

import { batchSanityFetch } from '@/lib/sanity/edge-fetcher';

const data = await batchSanityFetch({
  posts: { query: '*[_type == "post"][0..10]' },
  authors: { query: '*[_type == "author"]' },
  categories: { query: '*[_type == "category"]' }
}, 'production');

// data.posts, data.authors, data.categories

Draft Mode Support

The fetcher automatically handles Sanity's perspective API for draft/preview mode:

// When useAuth is true, the fetcher sets perspective: 'previewDrafts'
const authenticatedFetcher = createEdgeSanityFetcher('production', true);

// In your page/component
import { draftMode } from 'next/headers';

export async function MyPage() {
  const { isEnabled } = await draftMode();
  
  // Choose fetcher based on draft mode
  const fetcher = isEnabled ? fetchers.authenticated : fetchers.basic;
  const content = await fetcher<PageContent>(query);
  
  // With previewDrafts perspective, queries for 'myDoc' 
  // will return 'drafts.myDoc' if it exists
}

How It Works

  1. Without Authentication: Queries use default perspective (published only)
  2. With Authentication + Token: Queries use perspective: 'previewDrafts'
  3. Draft Resolution: When querying _id == "myDoc" with previewDrafts:
    • Returns draft version (drafts.myDoc) if it exists
    • Falls back to published version if no draft exists
    • This is handled automatically by Sanity's API

Required Environment Variable

SANITY_VIEWER_TOKEN=your-token-with-viewer-role

Real-time Updates

Server-Sent Events (Vercel)

  1. Copy examples/vercel-sse.ts to app/api/sanity-updates/route.ts
  2. Use the client helper:
import { createSanityEventSource } from '@/lib/sanity/edge-fetcher';

const eventSource = createSanityEventSource('*[_type == "post"]', 'production', {
  onMessage: (data) => {
    if (data.type === 'update') {
      console.log('Documents updated:', data.documents);
      // Update your UI
    }
  },
  onError: (error) => {
    console.error('SSE error:', error);
  }
});

// Cleanup when done
eventSource.close();

WebSockets (Cloudflare)

  1. Deploy examples/cloudflare-websocket.ts as a Cloudflare Worker
  2. Connect from client:
const ws = new WebSocket('wss://your-worker.workers.dev/ws');

ws.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'update') {
    // Handle updates
  }
});

Configuration

All configuration is centralized in config.ts:

import { config, fetchers, createCustomFetcher } from '@/lib/sanity/edge-fetcher';

// Access configuration
console.log(config.cache.ttl.default);  // 60 seconds
console.log(config.sanity.projectId);   // From env vars

// Use pre-configured fetchers
const posts = await fetchers.cached('*[_type == "post"]');
const settings = await fetchers.static('*[_type == "siteSettings"][0]');

// Create custom fetcher
const myFetcher = createCustomFetcher({
  ttl: 300,
  prefix: 'custom:',
  useCache: true,
  useRetry: true
});

Available Fetchers

| Fetcher | Use Case | Cache TTL | Features | |---------|----------|-----------|----------| | basic | Testing, one-off queries | None | No cache, no retry | | authenticated | Draft preview | None | Auth token included | | cached | Most queries | 60s | Multi-layer cache | | static | Global settings | 24h | Long cache, Redis | | dynamic | User data | 30s | Short cache, no Next.js | | page | Full pages | 1h | Page-optimized cache | | section | Page sections | 60s | Component cache |

Environment Variables

Required:

  • NEXT_PUBLIC_SANITY_PROJECT_ID - Your Sanity project ID
  • NEXT_PUBLIC_SANITY_API_VERSION - API version (e.g., '2025-02-10')

Optional:

  • SANITY_VIEWER_TOKEN - For authenticated requests (draft preview)
  • NEXT_PUBLIC_SANITY_DATASET - Dataset name (default: 'production')

For Redis caching (optional):

  • KV_REST_API_URL or UPSTASH_REDIS_REST_URL
  • KV_REST_API_TOKEN or UPSTASH_REDIS_REST_TOKEN
  • KV_REST_API_READ_ONLY_TOKEN (optional, for read-only operations)

API Reference

Core Functions

edgeSanityFetch<T>(options)

Main fetching function.

Options:

  • dataset: string - Sanity dataset name
  • query: string - GROQ query
  • params?: object - Query parameters
  • useCdn?: boolean - Use Sanity CDN (default: false)
  • useAuth?: boolean - Include auth token (default: false)

Returns: Promise<T> - Query result

createEdgeSanityFetcher(dataset, useAuth?)

Creates a reusable fetcher for a specific dataset.

Enhanced Functions

edgeSanityFetchWithRetry<T>(options, retryOptions?)

Fetch with automatic retry (requires p-retry).

createCachedSanityFetcher(dataset, revalidate?, tags?)

Creates a cached fetcher using Next.js cache.

batchSanityFetch<T>(queries, dataset, options?)

Fetch multiple queries in parallel.

createSanityEventSource(query, dataset?, options?)

Create SSE connection for real-time updates.

Trade-offs vs Official Client

| Feature | Edge Fetcher | Official Client | |---------|-------------|-----------------| | Bundle Size | ~2KB | ~50KB | | Edge Runtime | ✅ | ❌ | | Static Generation | ✅ | ⚠️ (with config) | | Auto Retry | ⚠️ (with p-retry) | ✅ | | Response Cache | ⚠️ (via Next.js) | ✅ | | Real-time | ⚠️ (via SSE/WS) | ✅ | | Mutations | ❌ | ✅ | | Assets | ❌ | ✅ |

Migration from Official Client

// Before (official client)
import { client } from '@/lib/sanity/client';
const posts = await client.fetch('*[_type == "post"]');

// After (edge fetcher)
import { edgeSanityFetch } from '@/lib/sanity/edge-fetcher';
const posts = await edgeSanityFetch({
  dataset: 'production',
  query: '*[_type == "post"]'
});

License

MIT © Invisible Cities Agency

Contributing

Feel free to submit issues and PRs. This is a focused utility, so features should maintain Edge Runtime compatibility.