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 🙏

© 2026 – Pkg Stats / Ryan Hefner

bertui-pagebuilder

v1.0.1

Published

Blazing fast static site generator - Elysia for API + Bun for files

Readme

BERTUI PAGEBUILDER v1.0 🚀

The 4th Official Son of the Bertui Family - The Blazing Fast Static Site Generator from Any API

265ms builds? Meet 1.4ms/page generation! From API to pre-rendered Server Islands in under a second.


🎯 THE VISION

Static Site Generation from ANY data source → Pre-rendered Server Islands

# Magic happens here:
bun run pagebuilder   # Fetches API → Generates .jsx files (1.4ms/page)
bun run build         # Bertui converts to HTML (265ms builds)

✨ WHY PAGEBUILDER?

| Feature | PageBuilder | Next.js SSG | Gatsby | Hugo | |---------|-------------|-------------|--------|------| | Build Speed | ⚡ 1.4ms/page | 🐢 50-100ms/page | 🐌 100ms+ | ⚡ Fast | | Zero Config | ✅ | ❌ | ❌ | ✅ | | Any Data Source | ✅ API/DB/JSON | ❌ Files only | ✅ | ❌ | | Server Islands | ✅ Native | ❌ | ❌ | ❌ | | Headers/Auth | ✅ Built-in | ❌ | ❌ | ❌ | | Dependencies | 2 (Elysia + ernest-logger) | 1000+ | 2000+ | 0 |


🚀 INSTALLATION

# In your Bertui project
npm install bertui-pagebuilder

# Or clone standalone
git clone https://github.com/bertui/pagebuilder
cd bertui-pagebuilder
bun install

📦 QUICK START

1. Create bertui.config.js

// bertui.config.js
export default {
  pageBuilder: {
    sources: [
      {
        name: "my-blog",
        endpoint: "https://api.example.com/posts",
        headers: {
          "Authorization": "Bearer YOUR_TOKEN"
        },
        method: "GET",
        template: "./templates/blog-post.jsx",
        output: "./pages/blog/[slug].jsx",
        dataStructure: {
          array: "data.posts",
          item: {
            slug: "slug",
            title: "title",
            content: "body.html",
            author: "author.name",
            date: "published_at"
          }
        },
        fallback: "NULL"
      }
    ]
  }
};

2. Create Template

// templates/blog-post.jsx
export default function BlogPost() {
  return (
    <article>
      <h1>{title}</h1>
      <div className="meta">
        <span>By {author}</span>
        <time dateTime="{date}">{new Date(date).toLocaleDateString()}</time>
      </div>
      <div dangerouslySetInnerHTML={{ __html: content }} />
      <footer>
        <p>Slug: {slug}</p>
      </footer>
    </article>
  );
}

3. Run It!

# Generate pages
bun run pagebuilder

# Build with Bertui
bun run build

🎨 TEMPLATE WRITING GUIDE

📝 The Golden Rules

1. Variables: Use {curlyBraces} NOT ${dollarBraces}

// ✅ CORRECT
<h1>{title}</h1>
<p>{content}</p>

// ❌ WRONG  
<h1>${title}</h1>
<p>${content}</p>

2. Attributes: Use Quotes " " Around Variables

// ✅ CORRECT - quotes around {date}
<time dateTime="{date}">{new Date(date).toLocaleDateString()}</time>
<img src="{imageUrl}" alt="{altText}" />

// ❌ WRONG - no quotes
<time dateTime={date}>...</time>    // Becomes: dateTime=2024-01-01 (invalid!)
<img src={imageUrl} alt={altText} /> // Becomes: src=https://... (might work but risky)

3. Conditional Rendering: JavaScript Expressions Stay { }

// ✅ CORRECT - JavaScript inside { }
{excerpt && <p className="excerpt">{excerpt}</p>}
{tags?.length > 0 && (
  <div className="tags">
    {tags.map(tag => <span key={tag}>{tag}</span>)}
  </div>
)}

// These {tags} and {excerpt} WON'T be replaced - they're JS expressions!

4. HTML Content: Use dangerouslySetInnerHTML for HTML

// ✅ CORRECT
<div dangerouslySetInnerHTML={{ __html: content }} />

// The {content} here refers to the JS variable, not template replacement

📊 Template Examples

Simple Blog Post

export default function BlogPost() {
  return (
    <article>
      <h1>{title}</h1>
      <div className="meta">
        <span>By {author}</span>
        <time dateTime="{date}">{new Date(date).toLocaleDateString()}</time>
      </div>
      <div className="content">
        {content}
      </div>
    </article>
  );
}

E-commerce Product

export default function Product() {
  const isOnSale = parseFloat(price) < parseFloat(originalPrice);
  
  return (
    <div className="product">
      <img src="{image}" alt="{name}" />
      <h2>{name}</h2>
      
      <div className="pricing">
        {isOnSale && (
          <>
            <span className="original-price">${originalPrice}</span>
            <span className="sale-badge">SALE</span>
          </>
        )}
        <span className="price">${price}</span>
      </div>
      
      <button className="add-to-cart">
        Add to Cart
      </button>
    </div>
  );
}

User Profile with Conditionals

export default function UserProfile() {
  return (
    <div className="profile">
      <img src="{avatar}" alt="{username}'s avatar" className="avatar" />
      
      <h1>{fullName}</h1>
      <p className="username">@{username}</p>
      
      {bio && <p className="bio">{bio}</p>}
      
      <div className="stats">
        <div className="stat">
          <strong>{postCount}</strong>
          <span>Posts</span>
        </div>
        <div className="stat">
          <strong>{followerCount}</strong>
          <span>Followers</span>
        </div>
      </div>
      
      {website && (
        <a href="{website}" className="website" target="_blank" rel="noopener">
          {website}
        </a>
      )}
    </div>
  );
}

⚙️ CONFIGURATION DEEP DIVE

Source Configuration Schema

{
  name: "unique-source-name",          // Required: For logging
  endpoint: "https://api.example.com", // Required: Any HTTP endpoint
  method: "GET",                       // Optional: GET, POST, etc. (default: GET)
  
  // Headers for authentication
  headers: {
    "Authorization": "Bearer ...",
    "Custom-Header": "value",
    "Content-Type": "application/json"
  },
  
  // POST body (if method is POST)
  body: {
    query: "{ posts { id title } }"
  },
  
  // Template configuration
  template: "./templates/my-template.jsx", // Required: Path to template
  output: "./pages/[category]/[id].jsx",   // Required: Output pattern
  fallback: "NULL",                        // Optional: Value for missing data
  
  // Data mapping - TELL PAGEBUILDER WHERE YOUR DATA IS
  dataStructure: {
    // Where's the array of items?
    array: "data.posts",  // Could be: "", "items", "response.data", etc.
    
    // Map template variables to data paths
    item: {
      // Template variable: API response path
      slug: "slug",                    // Simple: post.slug
      title: "title",                  // Simple: post.title
      content: "body.html",            // Nested: post.body.html
      author: "author.profile.name",   // Deep nested: post.author.profile.name
      date: "metadata.published_at",   // Different structure
      tags: "tags",                    // Arrays work too!
      imageUrl: "images[0].url"        // Array access
    }
  }
}

Advanced Configuration Examples

Multiple Sources

sources: [
  {
    name: "blog",
    endpoint: "https://cms.example.com/posts",
    template: "./templates/post.jsx",
    output: "./blog/[slug].jsx",
    dataStructure: { /* ... */ }
  },
  {
    name: "products", 
    endpoint: "https://shop.example.com/api/products",
    template: "./templates/product.jsx",
    output: "./shop/[sku].jsx",
    dataStructure: { /* ... */ }
  },
  {
    name: "docs",
    endpoint: "https://docs.example.com/api/pages",
    template: "./templates/post.jsx",  // Reuse same template!
    output: "./docs/[id].jsx",
    dataStructure: { /* ... */ }
  }
]

GraphQL API

{
  name: "graphql-posts",
  endpoint: "https://api.example.com/graphql",
  method: "POST",
  headers: {
    "Authorization": "Bearer ...",
    "Content-Type": "application/json"
  },
  body: {
    query: `
      query GetPosts {
        posts(limit: 100) {
          id
          title
          slug
          author {
            name
            avatar
          }
        }
      }
    `
  },
  dataStructure: {
    array: "data.posts",
    item: {
      id: "id",
      title: "title",
      slug: "slug",
      author: "author.name"
    }
  }
}

Nested API Response

// API returns: { response: { data: { items: [...] } } }
dataStructure: {
  array: "response.data.items",
  item: {
    id: "id",
    name: "name"
  }
}

// API returns: { results: [{ item: {...} }] }
dataStructure: {
  array: "results",
  item: {
    id: "item.id",
    title: "item.title"
  }
}

🎪 USE CASES

1. Blog/CMS Integration

// Headless CMS (Contentful, Sanity, Strapi, etc.)
{
  endpoint: "https://cdn.contentful.com/spaces/XXX/entries",
  headers: { "Authorization": "Bearer ..." },
  dataStructure: {
    array: "items",
    item: {
      title: "fields.title",
      slug: "fields.slug",
      content: "fields.body",
      image: "fields.image.fields.file.url"
    }
  }
}

2. E-commerce Store

// Shopify, WooCommerce, custom store
{
  endpoint: "https://store.example.com/api/products",
  dataStructure: {
    array: "products",
    item: {
      id: "id",
      name: "name",
      price: "price",
      description: "description",
      images: "images",
      category: "category.name"
    }
  },
  output: "./products/[id]/index.jsx"  // SEO-friendly nested paths
}

3. Documentation Site

// Git-based docs (READMEs, Markdown files)
{
  endpoint: "https://api.github.com/repos/owner/repo/contents/docs",
  headers: { "Authorization": "token ..." },
  dataStructure: {
    array: "",
    item: {
      slug: "name.replace('.md', '')",
      title: "name.replace('.md', '').replace(/-/g, ' ')",
      content: "download_url"  // Fetch markdown separately
    }
  }
}

4. Portfolio Site

// Behance, Dribbble, custom portfolio API
{
  endpoint: "https://api.behance.net/v2/users/xxx/projects",
  dataStructure: {
    array: "projects",
    item: {
      title: "name",
      description: "description",
      image: "covers[404]",
      url: "url",
      tags: "tags"
    }
  }
}

5. Real Estate Listings

{
  endpoint: "https://realtor.api/listings",
  dataStructure: {
    array: "listings",
    item: {
      id: "listing_id",
      address: "location.address",
      price: "price",
      bedrooms: "details.bedrooms",
      bathrooms: "details.bathrooms",
      images: "photos",
      description: "description"
    }
  },
  output: "./listings/[id].jsx"
}

⚡ PERFORMANCE

Benchmarks

10 pages    → 14ms total (1.4ms/page)
100 pages   → 140ms total (1.4ms/page)  
1000 pages  → 1.4s total (1.4ms/page)
10,000 pages → 14s total (1.4ms/page)

Why So Fast?

  1. Bun Native I/O - Filesystem operations at C++ speed
  2. Parallel Processing - Multiple sources processed simultaneously
  3. Batch Writes - Files written in chunks of 50
  4. Memory Caching - Templates, directories, config cached
  5. Zero Runtime - Build-time only, no client-side JS

🔧 ADVANCED FEATURES

Environment Variables

// In bertui.config.js
endpoint: process.env.API_URL || "https://api.example.com",
headers: {
  "Authorization": `Bearer ${process.env.API_TOKEN}`
}
# Run with env vars
API_TOKEN="secret" bun run pagebuilder

Multiple Output Patterns

output: "./[category]/[year]/[month]/[slug].jsx"
// Becomes: ./blog/2024/01/my-post.jsx

Custom Fallback Logic

fallback: "N/A",  // Shows "N/A" for missing data
// In template: <p>Price: {price || 'Contact for price'}</p>

Build Hooks

// Add to package.json scripts
"scripts": {
  "prepagebuilder": "echo 'Fetching latest data...'",
  "pagebuilder": "bun run src/cli.js",
  "postpagebuilder": "echo 'Generated $(find pages -name \"*.jsx\" | wc -l) pages'",
  "build": "bun run pagebuilder && bertui build"
}

🚨 ERROR HANDLING

Common Errors & Solutions

"Template file not found"

✅ Check: ls templates/
✅ Fix: Create template or fix path in config

"HTTP 401: Unauthorized"

✅ Check: headers.Authorization in config
✅ Fix: Add valid token: "Bearer your-token-here"

"Expected array, got object"

✅ Check: dataStructure.array path
✅ Fix: Point to array: "posts" not "posts[0]"

"Missing value for [slug]"

✅ Check: dataStructure.item mapping
✅ Fix: Map slug: "slug" to correct API field

Debug Mode

# See detailed logs
DEBUG=1 bun run pagebuilder

# See network requests
NETWORK_DEBUG=1 bun run pagebuilder

# Dry run (no file writes)
DRY_RUN=1 bun run pagebuilder

🧪 TESTING

Test Your Config

# Validate config
bun run pagebuilder --validate

# Test API connection
bun run pagebuilder --test-api

# Generate sample pages
bun run pagebuilder --sample 5

Mock Server for Development

// mock-server.js
import { serve } from 'bun';

serve({
  port: 3000,
  fetch(req) {
    return new Response(JSON.stringify({
      posts: Array.from({ length: 10 }, (_, i) => ({
        id: i + 1,
        title: `Post ${i + 1}`,
        slug: `post-${i + 1}`,
        content: `Content ${i + 1}`
      }))
    }));
  }
});

📈 PRODUCTION DEPLOYMENT

CI/CD Pipeline

# GitHub Actions
name: Deploy
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: oven-sh/setup-bun@v1
      - run: bun install
      - run: bun run pagebuilder
      - run: bun run build
      - run: bunx serve ./dist

Scheduled Builds

# Cron job (every hour)
0 * * * * cd /path/to/project && bun run pagebuilder && bun run build

Incremental Builds

// Only rebuild changed sources
const lastBuild = await readFile('.lastbuild', 'utf8').catch(() => '');
if (shouldRebuild(lastBuild)) {
  await runPageBuilder();
  await writeFile('.lastbuild', Date.now().toString());
}

Synergy

# The complete workflow
bertui create my-project      # CLI
cd my-project
s
# Edit bertui.config.js       # PageBuilder config
bun run pagebuilder          # Generate from API
bun run build               # Bertui Core builds

📚 RECIPES

Recipe 1: Blog with Comments

// Fetch posts
{
  endpoint: "https://api.example.com/posts",
  template: "./templates/post-with-comments.jsx",
  output: "./blog/[slug].jsx",
  dataStructure: { /* post fields */ }
}

// Fetch comments (separate source)
{
  endpoint: "https://api.example.com/comments?post={slug}",
  template: "./templates/comments.jsx",
  output: "./blog/[slug]/comments.jsx",
  dataStructure: { /* comment fields */ }
}

Recipe 2: Multi-language Site

sources: [
  {
    name: "posts-en",
    endpoint: "https://api.example.com/posts?lang=en",
    output: "./en/blog/[slug].jsx",
    // ...
  },
  {
    name: "posts-es",
    endpoint: "https://api.example.com/posts?lang=es",
    output: "./es/blog/[slug].jsx",
    // Same template, different data!
  }
]

Recipe 3: API Proxy with Transformation

// Custom server that transforms API response
import { serve } from 'bun';

serve({
  port: 3001,
  async fetch(req) {
    // Fetch from real API
    const response = await fetch('https://real-api.example.com', {
      headers: req.headers
    });
    
    // Transform to PageBuilder format
    const data = await response.json();
    const transformed = {
      posts: data.items.map(item => ({
        slug: item.id,
        title: item.name,
        // ... transform fields
      }))
    };
    
    return new Response(JSON.stringify(transformed));
  }
});

🌟 FINAL WORDS

Bertui PageBuilder isn't just another static site generator. It's:

⚡ SPEED - 1.4ms per page generation
🔗 CONNECTIVITY - Any API, any auth, any data structure
🎨 SIMPLICITY - Write templates, not data-fetching code
🚀 PERFORMANCE - Server Islands by default
❤️ DEVELOPER EXPERIENCE - Beautiful logs, clear errors

Welcome to the future of static sites. Welcome to Bertui PageBuilder - The 4th Son.


Built with ❤️ by the Bertui Family
Because static sites should be dynamic in creation, not in runtime.