bertui-pagebuilder
v1.0.1
Published
Blazing fast static site generator - Elysia for API + Bun for files
Maintainers
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?
- Bun Native I/O - Filesystem operations at C++ speed
- Parallel Processing - Multiple sources processed simultaneously
- Batch Writes - Files written in chunks of 50
- Memory Caching - Templates, directories, config cached
- 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 pagebuilderMultiple Output Patterns
output: "./[category]/[year]/[month]/[slug].jsx"
// Becomes: ./blog/2024/01/my-post.jsxCustom 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 fieldDebug 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 5Mock 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 ./distScheduled Builds
# Cron job (every hour)
0 * * * * cd /path/to/project && bun run pagebuilder && bun run buildIncremental 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.
