🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@page-speed/blocks

Package Overview
Dependencies
Maintainers
2
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@page-speed/blocks

High-performance rendering runtime for @opensite/ui components with pre-compiled Tailwind CSS and tree-shakable architecture

latest
Source
npmnpm
Version
0.3.0
Version published
Maintainers
2
Created
Source

@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
# or
npm install @page-speed/blocks
# or
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} />;
}

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;              // Unique identifier
  _type: string;            // Component type (maps to renderer)
  _parent?: string | null;  // Parent block ID (null for root)
  styles?: string;          // Tailwind CSS classes
  content?: string;         // Text content
  props?: Record<string, any>; // Component-specific props
  // ... additional properties
}

Built-in Renderers

The library includes optimized renderers for common component types that use @page-speed/pressable directly:

// Automatically registered block types:
- 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:

// Specific imports (recommended)
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";

// Custom renderers
import {
  pressableRenderer,
  buttonRenderer,
  linkRenderer
} from "@page-speed/blocks/renderers";

// Dynamic data feed layer
import { resolveBlocks, createFeedClient } from "@page-speed/blocks/data";

// Main export (includes all core functionality)
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[];           // Array of blocks to render
  className?: string;        // Optional CSS class for wrapper
  wrapper?: React.ComponentType<{ children: React.ReactNode }>;
  disableRouter?: boolean;   // Disable RouterProvider if app already has one
}

<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;  // Optional: disable router provider
}

Registry Functions

// Register a custom renderer for a block type
registerBlockRenderer(type: string, renderer: BlockRenderer): void

// Get renderer for a specific type
getBlockRenderer(type: string): BlockRenderer | undefined

// Check if a renderer exists
hasBlockRenderer(type: string): boolean

// Remove a renderer
unregisterBlockRenderer(type: string): void

// Clear all custom renderers
clearRegistry(): void

// Get all registered types
getRegisteredTypes(): string[]

// Register multiple renderers at once
registerRenderers(renderers: Record<string, BlockRenderer>): void

// Initialize default renderers (auto-called in browser)
initializeDefaultRenderers(): void

Utility Functions

// Parse design payload string to blocks
parseDesignPayload(payload: string | DesignPayload): Block[]

// Get root blocks (no parent)
getRootBlocks(blocks: Block[]): Block[]

// Get child blocks of a parent
getChildBlocks(blocks: Block[], parentId: string): Block[]

// Build element props from block
buildElementProps(block: Block): Record<string, any>

// Extract className from styles string
extractClassName(styles?: string): string

// Extract background styles
extractBackgroundStyle(styles?: string): React.CSSProperties | undefined

Pre-compiled CSS Support

For production environments, use pre-compiled Tailwind CSS:

// In your HTML/layout
<link rel="stylesheet" href="https://cdn.example.com/tailwind.css" />

// Then use BlocksRenderer normally
<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:

// This will use the optimized Pressable renderer
const buttonBlock: Block = {
  _id: "btn-1",
  _type: "Button",
  props: {
    variant: "default",
    size: "lg",
    onClick: () => console.log("Clicked!"),
  },
  content: "Click Me",
};

// Links also use Pressable
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} />;
}

Dynamic Data Feeds (@page-speed/blocks/data)

The ./data subpath is the client-side rendering data layer for Dynamic Data Feeds (see FEED_CONTRACT.md). It resolves symbolic dataSource descriptors carried on blocks into real props, then hands the resolved block tree to the synchronous renderer. The render engine itself is untouched — resolution is a pre-render async pass.

Symbolic sources

A block declares what data it wants via a block-level dataSource (a sibling of blockProps), never the data itself:

{
  "_type": "blog-grid-author-cards",
  "_id": "blk_a1",
  "blockProps": { "heading": "Latest news" },   // authored props — never overwritten
  "dataSource": { "type": "blog_feed", "limit": 9, "category": "news", "bindTo": "posts" }
}

resolveBlocks

import { resolveBlocks } from "@page-speed/blocks/data";
import { BlocksRenderer } from "@page-speed/blocks/core/renderer";

const resolved = await resolveBlocks(blocks, {
  baseUrl: process.env.FEEDS_API_ORIGIN!, // never hardcode the origin
  websiteToken,                           // websites.token — the single scoping identifier
  path: currentPath,                      // resolves blog_post { current: true } slugs
});

<BlocksRenderer blocks={resolved} />;

resolveBlocks finds blocks with a dataSource, fetches via FeedClient, maps the wire shape to props (BlogPostItem[] for blog_feed, detail props for blog_post), inlines them into the bind target (dataSource.bindTo → per-block default in DEFAULT_BIND_TARGETS"posts"), and attaches a machine-readable _feedMeta. The dataSource is retained on the output block so the client can re-query (pagination/filtering). Empty and error states are distinct:

  • _feedMeta.status === "empty" → the block renders its natural empty state (no fabricated items).
  • _feedMeta.status === "error" → routed to the __feed_error__ renderer when one is registered (otherwise renders the empty state).

Sources may expand one input block into many (expands: true, contract D6); pass custom resolvers via the sources option.

FeedClient

import { createFeedClient } from "@page-speed/blocks/data";

const client = createFeedClient({ baseUrl, websiteToken, fetcher });
const { data, meta, error } = await client.listBlogs({ page: 2, categorySlug: "news" });

FeedClient is the single place that builds feed URLs (/public_services/websites/{token}/feeds/...). It resends every provided filter on every page request (filters never drop across pages), clamps per_page to ≤ 50, URL-encodes values, and returns { data, meta, error } — it never throws for expected failures and never logs.

Error / fallback renderer keys

Exported constants: FALLBACK_RENDERER_KEY ("__fallback__") and FEED_ERROR_RENDERER_KEY ("__feed_error__"). Register a renderer under FEED_ERROR_RENDERER_KEY to surface feed errors distinctly from empty states.

SSR note

resolveBlocks is environment-agnostic (it uses plain fetch — inject one via the fetcher option in Node < 18 or tests). As with the rest of the package, auto-init is browser-only: on the server you must call initializeDefaultRenderers() manually before rendering the resolved blocks (and register any custom / __feed_error__ renderers).

Migration from @opensite/blocks

If migrating from @opensite/blocks:

  • Replace imports:
// Before
import { BlocksRenderer } from "@opensite/blocks";

// After
import { EnhancedBlocksRenderer } from "@page-speed/blocks";
  • Use EnhancedBlocksRenderer for automatic RouterProvider:
// Before
<BlocksRenderer blocks={blocks} />

// After
<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.

Keywords

react

FAQs

Package last updated on 10 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts