### Install Notablog CLI
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Install the Notablog command-line interface globally using npm. Ensure Node.js v15.0.0 or higher is installed.
```bash
npm i -g notablog
```
--------------------------------
### Minimal EJS Post Template Example
Source: https://context7.com/dragonman225/notablog/llms.txt
This is a basic example of an EJS template for rendering individual blog posts. It includes navigation, post title, date, and content.
```html
<%= post.title %> | <%= siteMeta.title %>
<%= post.title %>
<%- post.contentHTML %>
```
--------------------------------
### Notablog Generate Command Output
Source: https://context7.com/dragonman225/notablog/llms.txt
This is an example of the expected output when running the `notablog generate` command, showing the progress and completion time.
```text
[notablog] Copy theme assets
[notablog] Fetch site metadata
[notablog] Render home page and tags
[notablog] Fetch and render pages
[notablog] 3 of 10 posts have been updated
[notablog] 8 of 10 posts are published
[notablog] Done in 4.2s. Run 'notablog preview ./my-blog' to preview
```
--------------------------------
### Programmatic API - preview(workDir)
Source: https://context7.com/dragonman225/notablog/llms.txt
Opens the generated blog in a browser synchronously. Throws if previewBrowser is not configured.
```APIDOC
## Programmatic API — `preview(workDir)`
Opens the generated blog in a browser synchronously. Throws if `previewBrowser` is not configured.
### Parameters
- `workDir` (string): The working directory of the notablog-starter.
### Usage
```typescript
import { preview } from 'notablog'
try {
preview('./my-blog')
// Browser opens public/index.html in a detached process
} catch (error) {
// Throws: '"previewBrowser" property is not set in your Notablog config file.'
console.error(error.message)
}
```
```
--------------------------------
### Preview Generated Blog with Notablog CLI
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the `preview` command to open the generated `public/index.html` in a browser. The `help` command displays usage information.
```bash
notablog preview ./my-blog
```
```bash
notablog help
```
--------------------------------
### Clone Notablog Starter Repository
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Clone the starter repository to begin setting up your Notablog project. This provides a basic structure for your blog.
```bash
git clone https://github.com/dragonman225/notablog-starter.git
```
--------------------------------
### Notablog Starter Folder Structure
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Illustrates the directory layout for a Notablog starter project, including configuration, public assets, cache, and themes.
```tree
notablog-starter
├── config.json
├── public
├── cache
└── themes
├── pure
└── pure-ejs
```
--------------------------------
### Generate Static Site with Notablog CLI
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the `generate` command to build your static blog. Options include forcing a re-fetch of all pages (`--fresh`) and enabling verbose logging (`--verbose`).
```bash
notablog generate ./my-blog
```
```bash
notablog generate ./my-blog --fresh
```
```bash
notablog generate ./my-blog --verbose
```
```bash
notablog generate ./my-blog --fresh --verbose
```
--------------------------------
### CLI - notablog generate
Source: https://context7.com/dragonman225/notablog/llms.txt
Generates the entire static site from a notablog-starter directory. Fetches all Notion pages, renders the index, tag pages, and individual post pages, then writes output to /public/. Uses a file-based cache so only updated pages are re-fetched from Notion.
```APIDOC
## CLI - `notablog generate`
Generates the entire static site from a `notablog-starter` directory. Fetches all Notion pages, renders the index, tag pages, and individual post pages, then writes output to `/public/`. Uses a file-based cache so only updated pages are re-fetched from Notion.
### Usage
```bash
# Basic generation (reads config.json from ./my-blog)
notablog generate ./my-blog
# Force re-fetch all pages, ignoring the local cache
notablog generate ./my-blog --fresh
# Enable verbose / debug logging
notablog generate ./my-blog --verbose
# Combined flags
notablog generate ./my-blog --fresh --verbose
```
### Expected Output
```
[notablog] Copy theme assets
[notablog] Fetch site metadata
[notablog] Render home page and tags
[notablog] Fetch and render pages
[notablog] 3 of 10 posts have been updated
[notablog] 8 of 10 posts are published
[notablog] Done in 4.2s. Run 'notablog preview ./my-blog' to preview
```
```
--------------------------------
### CLI - notablog preview
Source: https://context7.com/dragonman225/notablog/llms.txt
Opens the generated public/index.html in a detached browser process. The browser executable path must be set in the starter's config.json under previewBrowser.
```APIDOC
## CLI - `notablog preview`
Opens the generated `public/index.html` in a detached browser process. The browser executable path must be set in the starter's `config.json` under `previewBrowser`.
### Usage
```bash
# Preview the generated blog
notablog preview ./my-blog
# Help text
notablog help
```
### Configuration (`config.json`)
`config.json` must contain the `previewBrowser` field:
```json
{
"url": "https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2",
"theme": "pure-ejs",
"previewBrowser": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"autoSlug": true,
"locales": "en-US"
}
```
```
--------------------------------
### Preview Blog with Notablog
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Preview your generated blog. You can specify a custom browser path in config.json or use the command with the path to your project directory.
```bash
notablog preview
```
--------------------------------
### Notablog Configuration for Preview
Source: https://context7.com/dragonman225/notablog/llms.txt
The `config.json` file must include the `previewBrowser` field to specify the browser executable path for the `preview` command.
```json
{
"url": "https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2",
"theme": "pure-ejs",
"previewBrowser": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"autoSlug": true,
"locales": "en-US"
}
```
--------------------------------
### Preview Blog Programmatically with Notablog
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the `preview` function from the Notablog library to open the generated blog in a browser. This function is synchronous and will throw an error if `previewBrowser` is not configured.
```typescript
import { preview } from 'notablog'
try {
preview('./my-blog')
// Browser opens public/index.html in a detached process
} catch (error) {
// Throws: '"previewBrowser" property is not set in your Notablog config file.'
console.error(error.message)
}
```
--------------------------------
### Generate Blog with Notablog
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Generate the blog content from your Notion table. Run this command inside the notablog-starter directory after configuring your Notion table URL in config.json.
```bash
notablog generate .
```
--------------------------------
### Programmatic API - generate(workDir, opts)
Source: https://context7.com/dragonman225/notablog/llms.txt
Generates the blog programmatically. Returns a Promise that resolves to 0 on success. Accepts an options object for concurrency, verbosity, and cache control.
```APIDOC
## Programmatic API — `generate(workDir, opts)`
Generates the blog programmatically. Returns a `Promise` that resolves to `0` on success. Accepts an options object for concurrency, verbosity, and cache control.
### Parameters
- `workDir` (string): The working directory of the notablog-starter.
- `opts` (object): Options for generation.
- `concurrency` (number): Number of parallel page fetch/render tasks (default: 3).
- `verbose` (boolean): Enable debug-level logging (default: false).
- `ignoreCache` (boolean): Set true to force re-fetch all pages (default: false).
### Usage
```typescript
import { generate } from 'notablog'
async function buildBlog() {
try {
const exitCode = await generate('./my-blog', {
concurrency: 5, // number of parallel page fetch/render tasks (default: 3)
verbose: false, // enable debug-level logging
ignoreCache: false, // set true to force re-fetch all pages
})
console.log(`Build finished with exit code ${exitCode}`) // 0 = success
} catch (error) {
console.error('Build failed:', error.message)
// Common errors:
// - Cannot find theme "pure-ejs" in themes/
// - Required property "publish" is missing in the Notion table.
// - Failed to load config from "./my-blog/config.json".
}
}
buildBlog()
```
```
--------------------------------
### Manage Notablog Configuration
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the Config class to read, update, and persist blog configuration from a JSON file. Ensure the config file path is correctly provided.
```typescript
import { Config } from 'notablog/dist/config'
const config = new Config('./my-blog/config.json')
// Read values
const notionUrl: string = config.get('url')
const theme: string = config.get('theme')
const locales: string | string[] | undefined = config.get('locales')
const autoSlug: boolean = config.get('autoSlug')
// Update a value in memory
config.set('theme', 'pure-ejs')
config.set('locales', ['en-US', 'zh-TW'])
// Persist changes to config.json
await config.sync()
```
--------------------------------
### Generate Blog Programmatically with Notablog
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the `generate` function from the Notablog library to build your site programmatically. Configure concurrency, verbosity, and cache behavior via the options object. The function returns a Promise that resolves to an exit code.
```typescript
import { generate } from 'notablog'
async function buildBlog() {
try {
const exitCode = await generate('./my-blog', {
concurrency: 5, // number of parallel page fetch/render tasks (default: 3)
verbose: false, // enable debug-level logging
ignoreCache: false, // set true to force re-fetch all pages
})
console.log(`Build finished with exit code ${exitCode}`) // 0 = success
} catch (error) {
console.error('Build failed:', error.message)
// Common errors:
// - Cannot find theme "pure-ejs" in themes/
// - Required property "publish" is missing in the Notion table.
// - Failed to load config from "./my-blog/config.json".
}
}
buildBlog()
```
--------------------------------
### TemplateProvider Class
Source: https://context7.com/dragonman225/notablog/llms.txt
Provides a lazy-loading cache for HTML template files, reading them from a specified layouts directory.
```APIDOC
## `TemplateProvider` class
Lazy-loading template cache that reads HTML template files from a layouts directory by name (without `.html` extension). Used internally by renderer strategies.
```typescript
import { TemplateProvider } from 'notablog/dist/templateProvider'
const provider = new TemplateProvider('./my-blog/themes/pure-ejs/layouts')
// Load and cache the template (reads from layouts/index.html)
const indexTemplate = provider.get('index')
console.log(indexTemplate.content) // EJS template string
console.log(indexTemplate.filePath) // Absolute path to the .html file
// If template doesn't exist, returns an error string instead of throwing:
const missing = provider.get('nonexistent')
// missing.content === 'Cannot find "nonexistent.html" in ".../layouts".'
```
```
--------------------------------
### Update Blog with Notablog
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Re-generate your blog content to reflect any updates made to your Notion table. This command can be run from inside or outside the project directory.
```bash
notablog generate
```
--------------------------------
### Config Class
Source: https://context7.com/dragonman225/notablog/llms.txt
Manages the config.json file, providing typed accessors and a sync method to persist changes.
```APIDOC
## `Config` class
Reads and manages the `config.json` file from a `notablog-starter` directory. Provides typed `get`/`set` accessors and an async `sync()` method to persist changes back to disk.
```typescript
import { Config } from 'notablog/dist/config'
const config = new Config('./my-blog/config.json')
// Read values
const notionUrl: string = config.get('url')
const theme: string = config.get('theme')
const locales: string | string[] | undefined = config.get('locales')
const autoSlug: boolean = config.get('autoSlug')
// Update a value in memory
config.set('theme', 'pure-ejs')
config.set('locales', ['en-US', 'zh-TW'])
// Persist changes to config.json
await config.sync()
```
The full `NotablogStarterConfig` interface:
```typescript
interface NotablogStarterConfig {
url: string // Public Notion collection page URL
theme: string // Theme folder name inside themes/
previewBrowser: string // Absolute path to browser binary
autoSlug: boolean // Auto-generate URL slugs from page titles
locales: string | string[] | undefined // BCP 47 locale(s) for date formatting
}
```
```
--------------------------------
### Notablog Theme manifest.json Configuration
Source: https://context7.com/dragonman225/notablog/llms.txt
The manifest.json file is required in your theme directory. It declares the Notablog version and the template engine used.
```json
{
"notablogVersion": "^0.6.0",
"templateEngine": "ejs"
}
```
--------------------------------
### Renderer and Strategies
Source: https://context7.com/dragonman225/notablog/llms.txt
Decouples template engines from rendering logic using a strategy pattern. Supports EJS (default) and Squirrelly.
```APIDOC
## `Renderer` / `EJSStrategy` / `SqrlStrategy`
Strategy-pattern renderer that decouples template engines from rendering logic. `EJSStrategy` (default) uses EJS; `SqrlStrategy` uses Squirrelly. Both load templates lazily from disk via `TemplateProvider`.
```typescript
import { Renderer, EJSStrategy, SqrlStrategy } from 'notablog/dist/renderer'
// --- EJS (default, used by the built-in "pure-ejs" theme) ---
const ejsStrategy = new EJSStrategy('./my-blog/themes/pure-ejs/layouts')
const renderer = new Renderer(ejsStrategy)
// Render the index page
const indexHtml = renderer.render('index', {
siteMeta: {
title: 'My Blog',
iconUrl: undefined,
cover: undefined,
descriptionPlain: 'A blog about things.',
descriptionHTML: '
A blog about things.
',
pages: [], // PageMetadata[]
tagMap: new Map(),
},
})
// Render a post page (template name comes from the "template" column in Notion)
const postHtml = renderer.render('post', {
siteMeta: { /* SiteContext */ },
post: {
id: 'abc123',
title: 'Hello World',
url: 'hello-world-abc123.html',
publish: true,
inMenu: false,
inList: true,
tags: [{ value: 'tech', color: 'blue' }],
date: '2024-01-15',
dateString: 'Mon, Jan 15, 2024',
descriptionPlain: 'My first post.',
descriptionHTML: '
My first post.
',
contentHTML: '
Hello World
Content here...
',
},
})
// --- Squirrelly alternative ---
const sqrlStrategy = new SqrlStrategy('./my-blog/themes/my-sqrl-theme/layouts')
const sqrlRenderer = new Renderer(sqrlStrategy)
const html = sqrlRenderer.render('index', { siteMeta: { /* ... */ } })
```
```
--------------------------------
### Fetch and Parse Notion Collection Page
Source: https://context7.com/dragonman225/notablog/llms.txt
Use this function to fetch data from a public Notion collection page and convert it into a SiteContext object. Ensure the Notion page has the required columns.
```typescript
import { createAgent } from 'notionapi-agent'
import { parseTable } from 'notablog/dist/parseTable'
import { Config } from 'notablog/dist/config'
const notionAgent = createAgent({ debug: false })
const config = new Config('./my-blog/config.json')
const siteContext = await parseTable(
'https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2',
notionAgent,
config
)
// SiteContext shape:
console.log(siteContext.title) // Blog name from the collection page
console.log(siteContext.descriptionPlain) // Plain-text description
console.log(siteContext.pages.length) // Total number of rows in the table
console.log(siteContext.tagMap.get('tech')) // PageMetadata[] for the "tech" tag
// Each PageMetadata in siteContext.pages:
const page = siteContext.pages[0]
console.log(page.id) // Notion page ID (no dashes)
console.log(page.title) // Page title
console.log(page.url) // Generated filename, e.g. "hello-world-abc123.html"
console.log(page.publish) // true/false — whether to render this page
console.log(page.inMenu) // true/false — show in nav bar
console.log(page.inList) // true/false — show in article list
console.log(page.template) // EJS template name, e.g. "post"
console.log(page.tags) // [{ value: 'tech', color: 'blue' }, ...]
console.log(page.date) // "2024-01-15" (ISO date string or undefined)
console.log(page.dateString) // "Mon, Jan 15, 2024" (locale-formatted)
console.log(page.lastEditedTime) // Unix ms timestamp for cache invalidation
```
--------------------------------
### Render Content with EJS or Squirrelly
Source: https://context7.com/dragonman225/notablog/llms.txt
Use the Renderer class with either EJSStrategy or SqrlStrategy to render templates. Templates are loaded lazily from disk. Ensure the correct layout directory is specified.
```typescript
import { Renderer, EJSStrategy, SqrlStrategy } from 'notablog/dist/renderer'
// --- EJS (default, used by the built-in "pure-ejs" theme) ---
const ejsStrategy = new EJSStrategy('./my-blog/themes/pure-ejs/layouts')
const renderer = new Renderer(ejsStrategy)
// Render the index page
const indexHtml = renderer.render('index', {
siteMeta: {
title: 'My Blog',
iconUrl: undefined,
cover: undefined,
descriptionPlain: 'A blog about things.',
descriptionHTML: '
A blog about things.
',
pages: [], // PageMetadata[]
tagMap: new Map(),
},
})
// Render a post page (template name comes from the "template" column in Notion)
const postHtml = renderer.render('post', {
siteMeta: { /* SiteContext */ },
post: {
id: 'abc123',
title: 'Hello World',
url: 'hello-world-abc123.html',
publish: true,
inMenu: false,
inList: true,
tags: [{ value: 'tech', color: 'blue' }],
date: '2024-01-15',
dateString: 'Mon, Jan 15, 2024',
descriptionPlain: 'My first post.',
descriptionHTML: '
My first post.
',
contentHTML: '
Hello World
Content here...
',
},
})
// --- Squirrelly alternative ---
const sqrlStrategy = new SqrlStrategy('./my-blog/themes/my-sqrl-theme/layouts')
const sqrlRenderer = new Renderer(sqrlStrategy)
const html = sqrlRenderer.render('index', { siteMeta: { /* ... */ } })
```
--------------------------------
### Notablog Theme Folder Structure
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Defines the standard directory layout for a Notablog theme, including layouts, assets, and the manifest file.
```tree
├── layouts
├── assets
└── manifest.json
```
--------------------------------
### Notablog Manifest JSON Configuration
Source: https://github.com/dragonman225/notablog/blob/master/README.md
Specifies the configuration options for a Notablog theme, including the supported Notablog version and the template engine.
```json
{
"notablogVersion": "string",
"templateEngine": "string"
}
```
--------------------------------
### Implement File-Based Cache
Source: https://context7.com/dragonman225/notablog/llms.txt
Utilize the Cache class for efficient, file-based caching of serialized data. The directory is auto-created if it doesn't exist. Use `shouldUpdate` to check for modifications.
```typescript
import { Cache } from 'notablog/dist/cache'
const cache = new Cache('./my-blog/cache') // auto-creates directory if missing
// Store any JSON-serializable object
cache.set('notion', 'page-id-abc123', { title: 'My Post', content: '...' })
// Retrieve stored object (returns undefined if missing or corrupted)
const data = cache.get('notion', 'page-id-abc123')
if (data) {
console.log(data) // { title: 'My Post', content: '...' }
}
// Check if the page was updated after the last cache write
const lastEditedTime = 1691000000000 // Unix ms timestamp from Notion
const needsUpdate = cache.shouldUpdate('notion', 'page-id-abc123', lastEditedTime)
// Returns true → page is newer than cache, re-fetch required
// Returns false → cached version is still current
// Compute the hashed file path for a cached entry
const filePath = cache.fPath('notion', 'page-id-abc123')
// e.g. './my-blog/cache/a3f7c2b9...'
```
--------------------------------
### Cache Class
Source: https://context7.com/dragonman225/notablog/llms.txt
A file-based cache for storing serialized NAST page trees, used to skip re-fetching unchanged Notion pages.
```APIDOC
## `Cache` class
A namespace-scoped, file-based cache backed by SHA-256-hashed filenames. Used internally to store serialized NAST page trees and skip re-fetching unchanged Notion pages.
```typescript
import { Cache } from 'notablog/dist/cache'
const cache = new Cache('./my-blog/cache') // auto-creates directory if missing
// Store any JSON-serializable object
cache.set('notion', 'page-id-abc123', { title: 'My Post', content: '...' })
// Retrieve stored object (returns undefined if missing or corrupted)
const data = cache.get('notion', 'page-id-abc123')
if (data) {
console.log(data) // { title: 'My Post', content: '...' }
}
// Check if the page was updated after the last cache write
const lastEditedTime = 1691000000000 // Unix ms timestamp from Notion
const needsUpdate = cache.shouldUpdate('notion', 'page-id-abc123', lastEditedTime)
// Returns true → page is newer than cache, re-fetch required
// Returns false → cached version is still current
// Compute the hashed file path for a cached entry
const filePath = cache.fPath('notion', 'page-id-abc123')
// e.g. './my-blog/cache/a3f7c2b9...'
```
```
--------------------------------
### Provide Templates Lazily
Source: https://context7.com/dragonman225/notablog/llms.txt
The TemplateProvider class manages a cache of HTML templates loaded from a specified directory. It reads templates lazily by name without the `.html` extension. Returns an error string for missing templates.
```typescript
import { TemplateProvider } from 'notablog/dist/templateProvider'
const provider = new TemplateProvider('./my-blog/themes/pure-ejs/layouts')
// Load and cache the template (reads from layouts/index.html)
const indexTemplate = provider.get('index')
console.log(indexTemplate.content) // EJS template string
console.log(indexTemplate.filePath) // Absolute path to the .html file
// If template doesn't exist, returns an error string instead of throwing:
const missing = provider.get('nonexistent')
// missing.content === 'Cannot find "nonexistent.html" in ".../layouts".'
```
--------------------------------
### Notablog Index Template Data
Source: https://github.com/dragonman225/notablog/blob/master/README.md
The data object passed to the `index.html` template, containing site-wide metadata.
```typescript
{
siteMeta: SiteContext
}
```
--------------------------------
### Notablog Post Template Data
Source: https://github.com/dragonman225/notablog/blob/master/README.md
The data object passed to the `post.html` template, containing site metadata, post details, and the generated HTML content.
```typescript
{
siteMeta: SiteContext
post: PageMetadata & { contentHTML: string } // All properties of PageMetadata plus contentHTML.
}
```
--------------------------------
### Notablog Tag Template Data
Source: https://github.com/dragonman225/notablog/blob/master/README.md
The data object passed to the `tag.html` template, including site metadata, the current tag name, and associated pages.
```typescript
{
siteMeta: SiteContext
tagName: string
pages: PageMetadata[]
}
```
=== COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.