@loomio/sdk
v0.1.2
Published
React SDK for integrating Loomio CMS with client websites
Readme
@loomio/sdk
Framework-agnostic SDK for integrating Loomio CMS with your client websites. This package provides multiple entry points for different use cases: core client, React hooks with server-side rendering support, and server-only utilities.
Installation
npm install @loomio/sdk
# or
pnpm add @loomio/sdk
# or
yarn add @loomio/sdkPackage Structure
This SDK is modular with separate entry points:
@loomio/sdk- Core client and global config management@loomio/sdk/react- React hooks with TanStack Query@loomio/sdk/server- Server-side utilities with caching support@loomio/sdk/client- Framework-agnostic client class
Quick Start
Global Configuration (Recommended)
Define your configuration once at the root of your application:
// lib/loomio-config.ts
import { defineConfig } from '@loomio/sdk';
export const loomioConfig = defineConfig({
siteId: "your-site-id",
apiUrl: "https://your-api.example.com", // Optional
});⚠️ Server-Side Considerations:
defineConfig()uses a module-level global variable- Call it ONCE during app initialization, NOT per-request
- In serverless/edge environments, ensure it's set during cold start
- For per-request config changes, use explicit config parameters instead
React with Global Config
// app/layout.tsx
import './lib/loomio-config'; // Import to initialize config
import { LoomioProvider } from '@loomio/sdk/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
<LoomioProvider>
{children}
</LoomioProvider>
</body>
</html>
);
}// components/HomePage.tsx
import { useSite, useGallery, useCustomValue } from '@loomio/sdk/react';
export function HomePage() {
const { data: site, isLoading } = useSite();
const { data: gallery } = useGallery();
const { data: tagline } = useCustomValue('site_tagline');
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{site?.name}</h1>
<p>{tagline?.value}</p>
<div className="gallery">
{gallery?.map(image => (
<img
key={image._id}
src={image.file?.url}
alt={image.name}
/>
))}
</div>
</div>
);
}Server-Side Rendering (Next.js, etc.)
// app/page.tsx
import { getSite, getGallery } from '@loomio/sdk/server';
import './lib/loomio-config'; // Ensure config is loaded
export default async function Page() {
// Uses global config, with optional overrides
const site = await getSite({ revalidate: 3600 }); // Revalidate every hour
const gallery = await getGallery({ tags: ['featured'] }, { revalidate: 300 });
return (
<div>
<h1>{site.name}</h1>
<div className="gallery">
{gallery.map(image => (
<img key={image._id} src={image.file?.url} alt={image.name} />
))}
</div>
</div>
);
}Theme Management
The SDK provides two-layer theme injection to eliminate theme flashing while maintaining reactivity:
1. Server-Side Theme Injection (Recommended)
Use the ThemeScript component to inject theme variables during server-side rendering, preventing the default theme from flashing:
// app/layout.tsx
import { ThemeScript } from '@loomio/sdk/server';
import { LoomioProvider, LoomioTheme } from '@loomio/sdk/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
{/* Inject theme on server (no flash) */}
<ThemeScript />
<LoomioProvider>
{/* Client component syncs theme changes */}
<LoomioTheme />
{children}
</LoomioProvider>
</body>
</html>
);
}Benefits:
- ✅ No theme flash on initial load
- ✅ Correct theme in initial HTML (SEO-friendly)
- ✅ Works with SSR/SSG
- ✅ Better performance (no client fetch needed)
- ✅ Zero configuration (uses global config automatically)
Advanced: Manual theme injection using generateThemeStyles():
import { getSite, generateThemeStyles } from '@loomio/sdk/server';
export default async function RootLayout({ children }) {
const site = await getSite({ revalidate: 3600 });
const themeCSS = generateThemeStyles(site.theme);
return (
<html>
<head>
<style dangerouslySetInnerHTML={{ __html: themeCSS }} />
</head>
<body>{children}</body>
</html>
);
}2. Client-Side Theme Component
The LoomioTheme component handles dynamic theme updates:
<LoomioProvider>
<LoomioTheme />
{children}
</LoomioProvider>The component:
- Syncs theme changes from the dashboard in real-time
- Handles system preference changes (light/dark mode toggle)
- Updates theme variables without page reload
- Works together with server-side injection
Theme Configuration Options
<LoomioTheme
// Respect system dark mode preference (default: true)
respectSystemPreference={true}
// Force a specific theme mode
forceMode="dark"
// Custom dark mode class name (default: "dark")
darkModeClass="dark-theme"
/>Using Theme Variables
Once injected, you can use the theme variables in your CSS:
.my-component {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
}
.button-primary {
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}Available CSS variables include:
--background,--foreground--primary,--primary-foreground--secondary,--secondary-foreground--muted,--muted-foreground--accent,--accent-foreground--destructive,--destructive-foreground--border,--input,--ring--card,--card-foreground--popover,--popover-foreground--sidebar-*(various sidebar colors)--chart-1through--chart-5--radius(border radius)
Available Hooks
useSite()
Fetches site metadata including name, description, pages, contact info, and social media.
const { data: site, isLoading, error } = useSite();useGallery(options?)
Fetches gallery images with optional tag filtering.
const { data: images } = useGallery({
tags: ['featured'],
enabled: true
});useCustomValue(key)
Fetches a single custom value by key.
const { data: value } = useCustomValue('hero_title');useCustomValues()
Fetches all custom values for the site.
const { data: values } = useCustomValues();Configuration
Configuration Priority
The SDK uses the following priority order for configuration:
- Global config (from
defineConfig()) merged with explicit overrides - Explicit config parameter (if it contains
siteId) - Environment variables (you can use any environment variable name you prefer)
Configuration Options
siteId(required): The ID of the site to fetch data forapiUrl(optional): Your Loomio API base URL. Falls back to environment or defaults
Alternative: Explicit Config (Without Global Config)
You can also use explicit configuration without global config:
// For React
<LoomioProvider
config={{
siteId: 'your-site-id',
apiUrl: 'https://your-api.example.com',
}}
>
<YourApp />
</LoomioProvider>// For server-side
const site = await getSite({
siteId: 'your-site-id',
apiUrl: 'https://your-api.example.com',
revalidate: 3600,
});Environment Variables
You can use any environment variable names you prefer. The SDK doesn't enforce specific environment variable names - you configure them when calling defineConfig(). Common examples include:
NEXT_PUBLIC_LOOMIO_SITE_ID,SITE_ID,LOOMIO_SITE_ID, etc. - Your site IDNEXT_PUBLIC_LOOMIO_API_URL,API_URL,LOOMIO_API_URL, etc. - Your API URL
Simply pass the environment variable values to defineConfig() using whatever variable names fit your project's conventions.
Multiple Sites
If you need to work with multiple sites in the same application, don't use global config. Instead, pass explicit config to each provider/function call:
// Site 1
<LoomioProvider config={{ siteId: 'site-1' }}>
<Site1Content />
</LoomioProvider>
// Site 2
<LoomioProvider config={{ siteId: 'site-2' }}>
<Site2Content />
</LoomioProvider>Features
- 🌐 Framework-Agnostic: Core client works with any JavaScript framework
- 🔄 Reactive Data: React hooks built on TanStack Query for automatic caching and refetching
- 🎯 Type-Safe: Full TypeScript support with generated types
- ⚡ Optimized: Smart caching strategies to minimize API calls
- 🚀 SSR Support: Server-side rendering with configurable caching (Next.js, Remix, etc.)
- 📦 Tree-Shakeable: Modular exports for optimal bundle size
- 🎨 Flexible: Global config or explicit config - you choose
- 🔌 Extensible: Easy to add custom hooks for your specific needs
Future Enhancements
This SDK is designed to grow with your needs. Future versions will include:
- Real-time subscriptions via Convex
- Product catalog hooks
- Search functionality
- Pagination support
- Optimistic updates
- And more!
Development
# Install dependencies
pnpm install
# Build the package
pnpm build
# Watch mode for development
pnpm dev
# Type checking
pnpm typecheckLicense
MIT
