@page-speed/blocks
High-performance rendering runtime for @opensite/ui components with pre-compiled Tailwind CSS, RouterProvider integration for Pressable components, and tree-shakable architecture.
Features
- Performance-First: Optimized for minimal bundle size and maximum runtime performance
- Tree-Shakable: Granular exports allow importing only what you need
- Flexible Styling: Works with both pre-compiled CSS and runtime Tailwind
- Registry-Based: Extensible component registry for custom renderers
- RouterProvider Integration: Automatic RouterProvider wrapping for Pressable components
- Direct Pressable Usage: Uses
@page-speed/pressable directly for proper Tailwind styling
- TypeScript: Full type safety with comprehensive type definitions
- React 18+: Built for modern React with hooks and concurrent features
Installation
pnpm add @page-speed/blocks
npm install @page-speed/blocks
yarn add @page-speed/blocks
Peer Dependencies
pnpm add react react-dom @opensite/ui @page-speed/img @page-speed/video @page-speed/pressable @page-speed/router
Quick Start
Basic Usage
import { BlocksRenderer } from "@page-speed/blocks";
import type { Block } from "@page-speed/blocks/types";
const blocks: Block[] = [
{
_id: "1",
_type: "Box",
styles: "p-4 bg-gray-100",
content: "Hello World",
},
];
function App() {
return <BlocksRenderer blocks={blocks} />;
}
Enhanced Usage (Recommended)
The EnhancedBlocksRenderer automatically wraps your blocks with RouterProvider from @page-speed/router, ensuring Pressable components work correctly:
import { EnhancedBlocksRenderer } from "@page-speed/blocks";
import type { Block } from "@page-speed/blocks/types";
const blocks: Block[] = [
{
_id: "1",
_type: "Button",
props: {
variant: "default",
size: "lg",
onClick: () => console.log("Clicked!"),
},
content: "Click Me",
},
];
function App() {
return <EnhancedBlocksRenderer blocks={blocks} />;
}
Manual Provider Control
If your app already has a RouterProvider, you can disable the automatic wrapping:
import { EnhancedBlocksRenderer } from "@page-speed/blocks";
function App() {
return (
<EnhancedBlocksRenderer
blocks={blocks}
disableRouter={true} // App already has RouterProvider
/>
);
}
Core Concepts
Block Structure
Blocks are the fundamental building units, compatible with Chai design payloads:
interface Block {
_id: string;
_type: string;
_parent?: string | null;
styles?: string;
content?: string;
props?: Record<string, any>;
}
Built-in Renderers
The library includes optimized renderers for common component types that use @page-speed/pressable directly:
- Pressable, PressableButton, PressableLink, CTAButton, ActionButton
- Button, SubmitButton, FormButton
- Link, NavLink, CTALink, ExternalLink
These renderers ensure proper Tailwind styling by using the Pressable component from @page-speed/pressable directly instead of pass-through components.
Component Registry
Extend the library with custom renderers:
import { registerBlockRenderer } from "@page-speed/blocks/registry";
registerBlockRenderer("MyComponent", ({ block, context }) => {
return (
<div className={block.styles}>
{block.content}
{context.renderChildren(block._id)}
</div>
);
});
Tree-Shakable Imports
Import only what you need for optimal bundle size:
import { EnhancedBlocksRenderer } from "@page-speed/blocks/core/enhanced";
import { BlocksProvider } from "@page-speed/blocks/core/provider";
import { registerBlockRenderer } from "@page-speed/blocks/registry";
import type { Block } from "@page-speed/blocks/types";
import {
pressableRenderer,
buttonRenderer,
linkRenderer
} from "@page-speed/blocks/renderers";
import {
EnhancedBlocksRenderer,
BlocksRenderer,
registerBlockRenderer,
initializeDefaultRenderers
} from "@page-speed/blocks";
API Reference
Components
<EnhancedBlocksRenderer />
The main component for rendering blocks with automatic RouterProvider wrapping:
interface EnhancedBlocksRendererProps {
blocks: Block[];
className?: string;
wrapper?: React.ComponentType<{ children: React.ReactNode }>;
disableRouter?: boolean;
}
<BlocksRenderer />
Base renderer without RouterProvider (for advanced use cases):
interface BlocksRendererProps {
blocks: Block[];
className?: string;
wrapper?: React.ComponentType<{ children: React.ReactNode }>;
}
<BlocksProvider />
Provider component for wrapping blocks with necessary context:
interface BlocksProviderProps {
children: React.ReactNode;
disableRouter?: boolean;
}
Registry Functions
registerBlockRenderer(type: string, renderer: BlockRenderer): void
getBlockRenderer(type: string): BlockRenderer | undefined
hasBlockRenderer(type: string): boolean
unregisterBlockRenderer(type: string): void
clearRegistry(): void
getRegisteredTypes(): string[]
registerRenderers(renderers: Record<string, BlockRenderer>): void
initializeDefaultRenderers(): void
Utility Functions
parseDesignPayload(payload: string | DesignPayload): Block[]
getRootBlocks(blocks: Block[]): Block[]
getChildBlocks(blocks: Block[], parentId: string): Block[]
buildElementProps(block: Block): Record<string, any>
extractClassName(styles?: string): string
extractBackgroundStyle(styles?: string): React.CSSProperties | undefined
Pre-compiled CSS Support
For production environments, use pre-compiled Tailwind CSS:
<link rel="stylesheet" href="https://cdn.example.com/tailwind.css" />
<EnhancedBlocksRenderer blocks={blocks} />
The library automatically works with pre-compiled styles, ensuring all button variants and component styles are properly applied.
Pressable Component Integration
The library includes direct integration with @page-speed/pressable for proper button and link styling:
const buttonBlock: Block = {
_id: "btn-1",
_type: "Button",
props: {
variant: "default",
size: "lg",
onClick: () => console.log("Clicked!"),
},
content: "Click Me",
};
const linkBlock: Block = {
_id: "link-1",
_type: "Link",
props: {
href: "/about",
variant: "link",
},
content: "Learn More",
};
Advanced Usage
Custom Block Renderer
import { registerBlockRenderer } from "@page-speed/blocks";
import { Pressable } from "@page-speed/pressable";
registerBlockRenderer("CustomCTA", ({ block, context }) => {
const { href, label, icon } = block.props || {};
return (
<Pressable
href={href}
variant="default"
size="lg"
className="my-custom-class"
>
{icon && <Icon name={icon} />}
{label || block.content}
{context.renderChildren(block._id)}
</Pressable>
);
});
Server-Side Rendering
import { renderToString } from "react-dom/server";
import { EnhancedBlocksRenderer } from "@page-speed/blocks";
const html = renderToString(
<EnhancedBlocksRenderer blocks={blocks} />
);
Dynamic Block Loading
import { BlocksRenderer, parseDesignPayload } from "@page-speed/blocks";
async function loadAndRenderBlocks() {
const response = await fetch("/api/blocks");
const payload = await response.json();
const blocks = parseDesignPayload(payload);
return <BlocksRenderer blocks={blocks} />;
}
Migration from @opensite/blocks
If migrating from @opensite/blocks:
import { BlocksRenderer } from "@opensite/blocks";
import { EnhancedBlocksRenderer } from "@page-speed/blocks";
- Use EnhancedBlocksRenderer for automatic RouterProvider:
<BlocksRenderer blocks={blocks} />
<EnhancedBlocksRenderer blocks={blocks} />
- The library automatically uses
@page-speed/pressable for button/link components, ensuring proper Tailwind styling.
Browser Support
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- React 17+ required
Contributing
See ARCHITECTURE.md for detailed architecture documentation.
License
BSD-3-Clause - see LICENSE for details.