Understanding the layout before you touch anything saves a lot of confusion later.

blog-doc/
├── index.js                   # Entry point — app wiring & server start
├── update.js                  # Runnable script — pull latest app version
├── package.json
├── .env                       # Optional — PORT override
├── .prettierignore            # Optional — Files and directories excluded from Prettier formatting
├── .prettierrc                # Optional — Prettier formatting configuration
├── app/
│   ├── content/
│   │   ├── pages/             # Page .md files (slug-as-filename)
│   │   ├── posts/             # Post .md files (slug-as-filename)
│   │   └── images/            # Uploaded images
│   ├── data/
│   │   ├── settings.json      # Site-wide settings
│   │   ├── menus.json         # Menu definitions
│   │   ├── active-theme.json  # Currently active theme ID
│   │   └── search.json        # Auto-generated search index
│   └── themes/
│       └── {themeId}/         # One directory per installed theme
│           ├── theme.json     # Theme manifest
│           ├── screenshot.*   # Required preview image
│           ├── templates/     # STE HTML templates
│           └── assets/        # CSS, JS, fonts, images
├── admin/
│   ├── assets/                # Admin CSS, JS, fonts
│   ├── functions/             # Pure data/business logic (no HTTP)
│   │   ├── admin-bar.js       # Dev-only admin bar middleware
│   │   ├── build.js           # Static site generator
│   │   ├── data.js            # File I/O helpers, PATHS, slug utilities
│   │   ├── frontmatter.js     # YAML frontmatter serialization
│   │   ├── search.js          # Search index generator
│   │   └── site-data.js       # Content assembly for templates
│   ├── routes/                # All route handler modules
│   └── views/                 # Admin HTML templates (STE)
├── defaults/                  # Default data and formatting files
├── scripts/
│   ├── build-cli.js           # Terminal entry point for the static site build
└── _site/                     # Generated static site (git-ignored)

Key directories

app/content/

This is where your content lives. Files are plain Markdown with YAML frontmatter. The filename (without .md) is the slug — about.md becomes /about.

app/data/

Plain JSON configuration files. You never need to edit these by hand — the admin GUI writes to them — but they're human-readable and easily version-controlled.

app/themes/

Each installed theme is a subfolder here. The active theme is tracked in active-theme.json by its folder name (the theme id).

admin/

The admin application itself. routes/ handles HTTP, functions/ is pure business logic with no HTTP dependencies. The clean separation means you can read and test build.js or site-data.js without spinning up the server.

Module responsibilities

Module Location Responsibility
admin-bar.js admin/functions/ Admin bar injected into live theme responses
build.js admin/functions/ Static site generator — pure, idempotent function
data.js admin/functions/ All file I/O, PATHS constants, slug utilities
frontmatter.js admin/functions/ Serialize form fields back to clean .md files
search.js admin/functions/ Generate and write search.json
site-data.js admin/functions/ Assemble template data objects per route

The adminBarMiddleware wraps res.end on every non-/admin request, checks for text/html content type, and injects the floating admin bar before </body>. The bar is absent from static builds. No changes to themes or templates are required.

defaults/

Contains the application's default template files used to initialize missing configuration and data files.

During setup, the ensureDefaultsFiles function checks whether the required destination files exist. If a file is missing, the corresponding file from this directory is copied to its expected location. Existing files are never overwritten, so user-customized settings and configurations are preserved.

scripts/

Holds build-cli.js. Runs the exact same build() function the GUI's POST /admin/build route calls, so behavior is identical regardless of trigger source.

Usage:

npm run build

_site/

Created on demand by the build engine. Add it to .gitignore. Every build wipes it and starts fresh.

Optional Formatting (Prettier)

Prettier integration is lazy-loaded and fully optional — it is never a hard dependency, never required for the build to succeed, and requires no configuration or feature flag.

To install Prettier in your project:

npm install --save-dev prettier

or

npm i -D prettier

Detection (getPrettier()): the module is imported once per process and cached. If the import fails because Prettier isn't installed, this is treated as a normal outcome (not an error) and every formatting call falls back to a raw copy for the rest of the build.

What gets formatted:

Stage Function Behavior
Rendered HTML (pages/posts/etc.) finalizeHtmlOutputformatHtml Formatted with the html parser, in the same pass as static-path rewriting
Theme assets + uploaded images copyWithOptionalFormat Each file is formatted with Prettier using its inferred parser. Binary files and files ignored by .prettierignore are copied unchanged.

Failure handling: a formatting error on any individual file (parse failure, unsupported syntax, etc.) is caught and that file falls back to a raw/unformatted copy — it never fails the build.

Warning: if Prettier isn't installed, a single warning is logged the first time formatting is attempted (warnPrettierMissingOnce), regardless of how many files or stages would have used it.

Build result reporting: stats.formatted (true/false) reflects whether Prettier was available for the HTML finalize stage. The build summary message appends "(Prettier not found — output not formatted)" when false.

Starter config: .prettierrc and .prettierignore ship at the project root as inert defaults — they configure Prettier's behavior if and when Prettier is present, but are never read by Blog-Doc's own code otherwise. The default .prettierignore excludes **/*.min.js, **/*.min.css, **/*.min.js.map and **/*.css.map so theme authors shipping pre-minified assets don't have them silently unminified during copy.

Architecture layers

Route registration order in index.js is intentional and must be preserved:

// 1. Admin GUI routes    → /admin/*
registerAdminUIRoutes(app)
// 2. Admin API routes    → /admin/api/*
const apiRouter = new LiteNode("__NO_STATIC_DIR__")
// ... register all API routes on apiRouter ...
app.nest("/admin/api", apiRouter)
// 3. Admin bar middleware (dev only — never runs during static build)
app.use(adminBarMiddleware)
// 4. Live theme routes   → /* (registered last — catch-all)
registerLiveTheme(app)

The live theme is registered last because its catch-all /:pagename route would swallow every other route if registered first.

The three main layers

Layer Base path Purpose
Admin GUI /admin/* Browser-based content management
Admin API /admin/api/* JSON REST API consumed by the GUI
Live theme /* Serves the active theme dynamically

All three share the same Node.js process and the same filesystem. There is no authentication — Blog-Doc is a local tool.

Updating Blog-Doc

update.js updates Blog-Doc to the latest version by fetching the newest code from the repository and refreshing dependencies — but only when they've actually changed.

Run this from your project root to update Blog-Doc whenever a new version is released:

node update.js

or

npm run update

Before updating, it checks whether you have uncommitted changes to tracked files and asks for confirmation before continuing. It also shows you the current and latest version before applying anything.

Your content and data are never touched by this script. The following are gitignored and therefore completely safe:

Folder Contents
app/content/ Pages, posts, and uploaded images.
app/data/ Site settings, navigation menus, and the active theme.
app/themes/ Installed themes (excluding the bundled default theme).