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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@prodeegi/scribe

v1.1.4

Published

Frontend SDK for Prodeegi CMS - Type-safe content client for React and Next.js

Readme

@prodeegi/scribe

Frontend SDK for Prodeegi CMS - Type-safe content client for React and Next.js.

Installation

npm install @prodeegi/scribe

Quick Start

1. Wrap your app with ProdegiProvider

import { ProdegiProvider } from '@prodeegi/scribe';

function App() {
  return (
    <ProdegiProvider
      config={{
        siteId: 'site_xxx',
        publicApiKey: 'pk_xxx',
        environment: 'production'
      }}
    >
      <YourApp />
    </ProdegiProvider>
  );
}

2. Use hooks to fetch content

import { usePage } from '@prodeegi/scribe';

function HomePage() {
  const { data: page, loading, error } = usePage('home');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{page.sections.hero.title}</h1>
      <p>{page.sections.hero.subtitle}</p>
    </div>
  );
}

API Reference

Configuration

interface ProdegiConfig {
  siteId: string;           // Your site ID from Prodeegi CMS
  publicApiKey: string;     // Your public API key (starts with pk_)
  environment: 'production' | 'preview';
  baseUrl?: string;         // Optional: Custom API base URL
}

React Hooks

usePage(pageKey: string)

Fetch an entire page's content.

const { data, loading, error, refetch } = usePage('home');

Returns:

  • data: Page content object with sections
  • loading: Boolean loading state
  • error: Error object if request failed
  • refetch: Function to manually refetch data

useSection(pageKey: string, sectionKey: string, options?)

Fetch a specific section from a page.

const { data, loading, error } = useSection('home', 'hero', {
  initialValues: {
    title: 'Welcome',
    subtitle: 'Loading...'
  },
  fallback: {
    title: 'Default Title'
  }
});

Options:

  • initialValues: Values to use while loading
  • fallback: Values to use if section not found

useField(path: string)

Fetch a specific field value using dot notation.

const { value, loading, error } = useField('home.hero.title');

Path format: pageKey.sectionKey.fieldKey

useCollection(collectionKey: string, options?)

Fetch a list of items from a repeatable collection (e.g. products, blog posts).

const { items, loading, error } = useCollection('products', {
  search: 'shoes',
  filter: { category: 'running' }
});

Options:

  • search: String to search against searchable fields
  • filter: Object for exact match filtering (JSONB)

useItem(collectionKey: string, slug: string)

Fetch a single item from a collection by its slug.

const { item, loading, error } = useItem('products', 'nike-air-max');

usePreview()

Manage preview mode for viewing draft content.

const { enabled, enablePreview, disablePreview } = usePreview();

// Enable preview mode
enablePreview('preview_token_xxx');

// Disable preview mode
disablePreview();

Core Client (Advanced)

For non-React usage or advanced scenarios:

import { createClient, createConfig } from '@prodeegi/scribe';

const config = createConfig({
  siteId: 'site_xxx',
  publicApiKey: 'pk_xxx',
  environment: 'production'
});

const client = createClient(config);

// Fetch page
const page = await client.getPage('home');

// Fetch section
const section = await client.getSection('home', 'hero');

// Fetch field
const value = await client.getField('home.hero.title');

// Enable preview
client.enablePreview('preview_token_xxx');

TypeScript Support

Scribe is written in TypeScript and provides full type definitions.

import type {
  ProdegiConfig,
  PageContent,
  SectionData,
  FieldValue,
  ContentSchema
} from '@prodeegi/scribe';

Preview Mode

Preview mode allows you to view draft content before publishing.

In React

import { usePreview } from '@prodeegi/scribe';

function PreviewBanner() {
  const { enabled, disablePreview } = usePreview();

  if (!enabled) return null;

  return (
    <div className="preview-banner">
      🔍 Preview Mode Active
      <button onClick={disablePreview}>Exit Preview</button>
    </div>
  );
}

Enabling Preview

// Get preview token from URL or CMS
const searchParams = new URLSearchParams(window.location.search);
const previewToken = searchParams.get('preview');

if (previewToken) {
  enablePreview(previewToken);
}

Next.js Integration

App Router

// app/layout.tsx
import { ProdegiProvider } from '@prodeegi/scribe';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ProdegiProvider
          config={{
            siteId: process.env.NEXT_PUBLIC_PRODEEGI_SITE_ID!,
            publicApiKey: process.env.NEXT_PUBLIC_PRODEEGI_API_KEY!,
            environment: process.env.NODE_ENV === 'production' ? 'production' : 'preview'
          }}
        >
          {children}
        </ProdegiProvider>
      </body>
    </html>
  );
}

Pages Router

// pages/_app.tsx
import { ProdegiProvider } from '@prodeegi/scribe';

export default function App({ Component, pageProps }) {
  return (
    <ProdegiProvider
      config={{
        siteId: process.env.NEXT_PUBLIC_PRODEEGI_SITE_ID!,
        publicApiKey: process.env.NEXT_PUBLIC_PRODEEGI_API_KEY!,
        environment: 'production'
      }}
    >
      <Component {...pageProps} />
    </ProdegiProvider>
  );
}

Environment Variables

Create a .env.local file:

NEXT_PUBLIC_PRODEEGI_SITE_ID=site_xxx
NEXT_PUBLIC_PRODEEGI_API_KEY=pk_xxx

Error Handling

Scribe follows a fail-safe philosophy:

  • Development: Errors logged to console with detailed messages
  • Production: Silent failures with console warnings, no crashes
  • Missing content: Returns null instead of throwing
  • Invalid config: Throws clear error messages

Best Practices

  1. Use ProdegiProvider at the root of your app
  2. Use environment variables for sensitive config
  3. Handle loading states in your UI
  4. Provide fallback content for better UX
  5. Use TypeScript for type safety

Examples

Feature List

function FeaturesSection() {
  const { data: features } = useSection('home', 'features');

  return (
    <div className="features-grid">
      {features?.items?.map((item, index) => (
        <div key={index} className="feature">
          {item.icon && <img src={item.icon.url} alt={item.icon.alt} />}
          <h3>{item.heading}</h3>
          <p>{item.body}</p>
        </div>
      ))}
    </div>
  );
}

Testimonials

function TestimonialsSection() {
  const { data: testimonials } = useSection('home', 'testimonials');

  return (
    <div className="testimonials">
      {testimonials?.items?.map((item, index) => (
        <blockquote key={index}>
          <p>"{item.quote}"</p>
          <footer>
            {item.avatar && <img src={item.avatar.url} alt={item.author} />}
            <cite>{item.author}</cite>
            {item.role && <span>{item.role}</span>}
          </footer>
        </blockquote>
      ))}
    </div>
  );
}

Collections (Products)

function ProductGrid() {
  const { items, loading } = useCollection('products', { search: 'premium' });

  if (loading) return <div>Loading...</div>;

  return (
    <div className="product-grid">
      {items.map(product => (
        <div key={product.itemSlug} className="product-card">
          <img src={product.data.image?.url} alt={product.data.name} />
          <h3>{product.data.name}</h3>
          <p>{product.data.price}</p>
        </div>
      ))}
    </div>
  );
}

License

MIT

Icon Component

The SDK includes a powerful Icon component that supports Tabler Icons (react-icons/tb) and Social Icons (FontAwesome 6 - react-icons/fa6).

It uses Dynamic Imports to ensure you only load the icon libraries you actually use, keeping your bundle size small.

import { Icon } from '@prodeegi/scribe/react';

function MyComponent() {
  return (
    <div>
      {/* Tabler Icon (Default) */}
      <Icon name="TbActivity" className="w-6 h-6" />
      
      {/* Social Icon (FontAwesome) - Prefix with 'fa/' */}
      <Icon name="fa/FaGithub" className="w-6 h-6" />
    </div>
  );
}

Note: The exact icon name string ("TbActivity", "fa/FaGithub") comes directly from the CMS API response when using icon or social-icon fields.

License

MIT

Support

For issues and questions, visit: https://github.com/prodeegi/vault/issues