Project Structure
Every file and directory in a Blog-Doc installation, and what each one does.
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.) | finalizeHtmlOutput → formatHtml |
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). |