@mel000000/weweb-dynamic-metadata
v1.0.19
Published
Generate dynamic metadata for WeWeb static exports
Maintainers
Readme
weweb-dynamic-metadata
⭐ Build-time SEO metadata generator for WeWeb static exports
A build-time tool that generates unique SEO metadata for each dynamic page in your WeWeb project. Zero runtime overhead, perfect SEO, and completely free.
Table of Contents
- Overview
- Why This Package Matters
- Who This Is For
- Why This Project Exists
- Usage Example
- Architecture
- Features
- Quick Start
- Prerequisites Checklist
- Setup
- How It Works
- Project Folder Transformation
- Output Summary
- Programmatic Usage
- Why Not Cloudflare Workers?
- Troubleshooting
- License
Overview
WeWeb exports static HTML files where all dynamic routes (like /article/1, /article/2) share the exact same HTML template with identical metadata. This is terrible for SEO - every article page looks identical to search engines.
This package solves that by generating a central metadata file and tiny reference HTML files that point to your main template. The result? Each article page gets its own unique metadata while maintaining a single source of truth.
Repository Structure
- 📦 This Package: The npm package that does the metadata generation
- 📦 Your WeWeb Project: Where you install and run it
Key challenges addressed:
- Complete generation in ~1 second for 100 articles
- Zero runtime overhead - all metadata pre-generated
- Tiny footprint - only ~500 bytes per article
Why This Package Matters
WeWeb currently has no built-in solution for dynamic SEO metadata. Every dynamic page shares the same HTML template, making SEO optimization impossible.
This package provides a production-tested solution that:
- Gives each page unique metadata
- Requires no serverless functions
- Works with any hosting platform
- Preserves all WeWeb dynamic functionality
Who This Is For
This package is ideal for:
- WeWeb users needing SEO for dynamic pages
- Developers deploying WeWeb to any static host
- Projects requiring unique metadata per page
- Teams wanting zero-latency SEO solution
This package may NOT be ideal if:
- Your content changes every minute (use server-side instead)
- You can't run build scripts in your deployment
- You need real-time metadata updates
Why This Project Exists
WeWeb's dynamic pages share HTML templates, making unique metadata impossible. Official solutions require Cloudflare Workers (cost, complexity, latency).
This project provides a simpler, cheaper, faster alternative:
- Zero runtime costs - Everything runs at build time
- Perfect SEO - Instant HTML for crawlers
- Dead simple - Just a config file and one command
- Works everywhere - Any static hosting works
Usage Example
- Export your WeWeb project (creates
dist/folder) - Create
weweb.config.jsin your project root - Run
npx @mel000000/weweb-dynamic-metadataornpx weweb-metadata - Deploy anywhere - each article now has unique metadata!
# One-time setup
npm install --save-dev @mel000000/weweb-dynamic-metadata
# Generate metadata (run after each WeWeb export)
npx @mel000000/weweb-dynamic-metadata
# That's it! Your articles now have unique SEO metadataFeatures
- 🚀 Zero Runtime Overhead: All metadata pre-generated at build time
- 📦 Tiny Footprint: Only ~500 bytes per article (reference files)
- 🎯 Perfect SEO: Each page gets unique titles, descriptions, and Open Graph tags
- 🔧 Simple Setup: Just add a config file and run
- 💸 Completely Free: No Cloudflare Workers, no serverless costs
- 🌍 Works Everywhere: Deploy to any static hosting (Netlify, Vercel, GitHub Pages, S3, etc.)
- ⚡ Fast: Generates 1000 articles in ~3 seconds
- 🔗 Commit Traceability: Optional git-info.js injection for deploy visibility
Quick Start
# 1. Install the package
npm install --save-dev @mel000000/weweb-dynamic-metadata
# 2. Create weweb.config.js in your project root
# (see Setup section below)
# 3. Run it!
npx @mel000000/weweb-dynamic-metadata
# Done! Your articles now have unique metadataGetting Started
Prerequisites Checklist
Before using this package, ensure you have:
- A WeWeb project exported to static files (has
your-page-name/_param/index.html) - Node.js 18 or higher installed
- A Supabase project with your content
- Your Supabase URL and anon key ready
- Your WeWeb build folder (usually
dist/or project root)
Setup
1. Configure Supabase
Create a view in your Supabase database for optimal performance:
-- Create a view for article metadata
CREATE VIEW article_metadata AS
SELECT
id,
title,
LEFT(content, 160) AS excerpt, -- First 160 chars for descriptions
image_url
FROM articles;
-- Enable public read access
ALTER VIEW article_metadata ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow public read access"
ON article_metadata
FOR SELECT
TO anon
USING (true);2. Get Your Supabase API Key
In your Supabase dashboard, go to Project Settings > API Keys. You'll see two types of keys:
| Key Type | Format | When to Use |
|----------|--------|-------------|
| Project URL | https://your-project.supabase.co | Always needed |
| anon / publishable key | sb_publishable_... or JWT | Legacy option (being phased out) |
| secret key (recommended) | sb_secret_... | ✅ Recommended for new projects |
Note: Supabase is transitioning away from the legacy anon key. For new projects, use the secret key (starts with
sb_secret_...). If you're using an older project, the anon key will continue working for now, but consider migrating to the new key format.
3. Set Up Environment Variables
Create a .env file in your project root to store your Supabase credentials securely:
# .env file
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-secret-or-anon-key-here⚠️ Important: Never commit your .env file to version control. Add it to your .gitignore:
# .gitignore
.envThe package uses dotenv to automatically load these environment variables when you run the generator.
4. Create Config File
Create weweb.config.js in your project root:
export default {
// Your Supabase configuration
supabase: {
url: process.env.SUPABASE_URL,
apikey: process.env.SUPABASE_KEY // Works with both anon and secret keys
},
// Optional: Specify your build folder (defaults to ./dist)
outputDir: "./dist",
// Define your dynamic routes
pages: [
{
route: "/your-page-name/:id",
table: "table-view-name", // Your Supabase table name
metadata: {
title: "title", // Database field for title
content: "excerpt", // Database field for description
image: "featured_image" // Database field for image
}
}
]
};5. Run the Generator
# One-time generation
npx @mel000000/weweb-dynamic-metadata
How It Works
1. Reads Your Config
The package reads weweb.config.js from your project root to understand your Supabase connection and dynamic routes.
2. Discovers Content IDs
Fetches all IDs from your Supabase table to know which articles need metadata.
3. Fetches Metadata
For each ID, retrieves the metadata fields you specified (title, content, image, etc.).
4. Generates Central Metadata
Creates a central JavaScript file with all metadata:
window.METADATA = {
"1": { title: "Article 1", content: "...", image: "..." },
"2": { title: "Article 2", content: "...", image: "..." },
// ... one entry per article
};5. Injects Script into Template
Adds the metadata injector script to your your-page-name/_param/index.html template.
6. Creates Reference Files
Generates tiny HTML files for each article that load the template and pass the article ID:
<!-- article/1/index.html - only ~500 bytes! -->
<script>
window.CURRENT_ARTICLE_ID = "1";
window.location.replace('../_param/index.html#1');
</script>Architecture
flowchart TD
%% Styles
classDef setup fill:#2da44e,stroke:#1a7f37,color:#ffffff
classDef core fill:#0969da,stroke:#0550ae,color:#ffffff
classDef output fill:#8250df,stroke:#6639ba,color:#ffffff
classDef runtime fill:#f66a0a,stroke:#bf4e00,color:#ffffff
classDef note fill:#fff,stroke:#6e7781,color:#24292f,stroke-width:1px,stroke-dasharray:3 3
subgraph Setup ["🟢 User Setup (One Time)"]
A["User exports WeWeb project<br/>creates static files"] --> B
B["User creates weweb.config.js<br/>in project root"] --> C
C["Configure:<br/>• Supabase URL & anonKey<br/>• Dynamic routes (/article/:id)<br/>• Table & metadata fields<br/>• Optional: outputDir"] --> D
D["Save config file"]
end
class A,B,C,D setup
subgraph Build ["🔵 Build Time - Metadata Generation"]
direction TB
E["Run: npx @mel000000/weweb-dynamic-metadata"] --> F
F["Read weweb.config.js"] --> G
G["Validate configuration"] --> I["For each dynamic route:<br/>e.g., /article/:id"]
I --> J["Discover content IDs<br/>GET /rest/v1/table?select=id"]
J --> K["IDs: [1, 2, 3, ...]"]
K --> L["Fetch metadata for each ID<br/>GET /rest/v1/table?id=eq.{id}"]
L --> M["Build central metadata object<br/>{1: {...}, 2: {...}, ...}"]
M --> N["Generate article/metadata.js<br/>window.METADATA = {...}"]
N --> O["Locate WeWeb template<br/>article/_param/index.html"]
O --> P["Inject metadata script into template"]
P --> Q["For each ID, create reference file:<br/>article/1/index.html<br/>article/2/index.html<br/>..."]
Q --> R["Copy metadata.js to _param/<br/>for backward compatibility"]
end
class E,F,G,I,J,K,L,M,N,O,P,Q,R core
subgraph Output ["🟣 Generated Output"]
S["📁 article/<br/>├── metadata.js<br/>├── _param/<br/>│ ├── index.html (modified)<br/>│ └── metadata.js<br/>├── 1/<br/>│ ├── index.html (reference)<br/>│ └── metadata.js<br/>├── 2/<br/>│ ├── index.html (reference)<br/>│ └── metadata.js<br/>└── ..."]
end
class S output
subgraph Runtime ["🟠 Runtime - Browser"]
T["User visits /article/2"] --> U
U["Browser loads article/2/index.html<br/>(tiny reference file)"] --> V
V["JavaScript sets window.CURRENT_ARTICLE_ID=2<br/>and loads _param/index.html"] --> W
W["Main template loads with ID=2"] --> X
X["Metadata injector reads window.METADATA[2]<br/>and updates page metadata"] --> Y
Y["Page displays with correct title,<br/>description, Open Graph tags"]
end
class T,U,V,W,X,Y runtime
%% Connections
D ==> E
R ==> S
S ==> T
%% Legend
L1["🟢 User Setup: One-time configuration"]:::note
L2["🔵 Build Time: Generates metadata + reference files"]:::note
L3["🟣 Output: Generated files ready for deployment"]:::note
L4["🟠 Runtime: Browser loads and applies metadata"]:::note
L5["⚙️ weweb.config.js: Central configuration file"]:::note
L1 ~~~ L2 ~~~ L3 ~~~ L4 ~~~ L5Project Folder Transformation
Before: WeWeb Export (No Metadata)
dist/ (or your build folder)
├── your-page-name/
│ └── _param/
│ └── index.html # Same for ALL articles!
├── assets/
├── index.html
└── ... The Problem: Every article at /your-page-name/1, /your-page-name/2, etc. serves the EXACT same HTML file with identical metadata.
After: Package Runs
dist/
├── your-page-name/
│ ├── metadata.js # Central metadata for all articles
│ ├── _param/
│ │ ├── index.html # Original template (script injected)
│ │ └── metadata.js # Copy for compatibility
│ ├── 1/
│ │ ├── index.html # Tiny reference file (~500 bytes)
│ │ └── metadata.js # Points to central metadata
│ ├── 2/
│ │ ├── index.html # Tiny reference file
│ │ └── metadata.js # Points to central metadata
│ └── ...
├── assets/
└── index.html
The Solution: Each article now has its own unique metadata while sharing the same template!
Output Summary
After running, you'll get a JSON summary:
{
"timestamp": "2024-03-14T20:49:28.825Z",
"pages": [
{
"route": "/article/:id",
"total": 150,
"succeeded": 150,
"failed": 0,
"metadataEntries": 150,
"referencesCreated": 150
}
],
"totalMetadataEntries": 150,
"outputDirectories": ["dist/article"],
"duration": "2.34"
}and an overview in the console:
[[email protected]] injecting env (2) from .env -- tip: 🛡️ auth for agents: https://vestauth.com
🚀 WeWeb Dynamic Metadata Generator
⏭️ Metadata injector already present in: article\_param\index.html
🧹 Found 2 duplicate injectors, cleaning up...
╔════════════════════════════════════════════════╗
║🎉 GENERATION COMPLETE ║
╟────────────────────────────────────────────────╢
║ ⏱️ Duration: 1.31s ║
║ 📊 Total entries: 9 ║
║ 📁 Output: article ║
╚════════════════════════════════════════════════╝Programmatic Usage
import { processFiles } from '@mel000000/weweb-dynamic-metadata';
const result = await processFiles();
console.log(`Generated ${result.totalMetadataEntries} metadata entries`);
console.log(`Took ${result.duration} seconds`);Why Not Cloudflare Workers?
| Approach | This Package | Cloudflare Worker | |----------|--------------|-------------------| | Runtime | None (pre-generated) | Each request | | Latency | 0ms | +100-300ms | | Cost | Free | Pay per request | | SEO | Perfect - instant HTML | Crawlers might timeout | | Complexity | Simple config | Worker deployment | | Scaling | Infinite (static files) | Worker limits | | Cold starts | None | Possible |
Troubleshooting
| Issue | Likely Cause | Solution |
|-------|--------------|----------|
| getOutputDir: Could not find build folder | No WeWeb export found | Run weweb export first or set outputDir in config |
| Config error: Invalid or unexpected token | BOM characters in config | Recreate file without BOM (use VSCode "Save with Encoding → UTF-8") |
| No metadata generated | Supabase connection issue | Check your Supabase URL and anon key |
| Failed to fetch ID | Table or field names wrong | Verify table and field names in config |
| Reference files not created | Permission issues | Check write permissions in build folder |
License
This project is licensed under the MIT License. See the LICENSE file for details.
