@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/scribeQuick 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 sectionsloading: Boolean loading stateerror: Error object if request failedrefetch: 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 loadingfallback: 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 fieldsfilter: 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_xxxError 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
nullinstead of throwing - Invalid config: Throws clear error messages
Best Practices
- Use ProdegiProvider at the root of your app
- Use environment variables for sensitive config
- Handle loading states in your UI
- Provide fallback content for better UX
- 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
iconorsocial-iconfields.
License
MIT
Support
For issues and questions, visit: https://github.com/prodeegi/vault/issues
