๐ hbh-deve
A flexible SSR view engine and dynamic file-based route loader for Express.js.
Supports.pug,.html,.md, and.jsviews โ with live reload, layouts, and developer-friendly DX.
โจ Features
- ๐ File-based routing โ Auto-maps
views/pages/structure to Express routes - ๐ง Multi-format views โ Render
.pug,.html,.md, and.jsas SSR templates - ๐ Live reload โ Watches files and reloads connected browsers on changes
- ๐งฉ Layouts โ Optional
layout.pugsupport with layout toggling per-view - ๐ฆ Frontmatter โ In Markdown and
.jsviews for metadata - ๐ ๏ธ Middleware support โ Per-view Express middleware via
.jsexports - ๐ง Configurable โ Customizable via
hbh-deve.config.jsor use same defaults - ๐งช Error overlays โ In-browser overlays for SSR/runtime errors
๐ฆ Installation
1npm install hbh-deve2# or3yarn add hbh-deve
๐๏ธ Project Structure
1project-root/2โโโ views/3โ โโโ layout.pug # Optional shared layout4โ โโโ pages/ # Route-driven views5โ โโโ index.pug # route: /6โ โโโ about.html # route: /about7โ โโโ blog/8โ โ โโโ [slug].md # dynamic: /blog/:slug9โ โโโ user/[id].js # dynamic: /user/:id10โโโ hbh-deve.config.js # Optional custom config11โโโ index.js # App entry
โ๏ธ Configuration (Optional)
1// hbh-deve.config.js2import path from 'path';4const __dirname = process.cwd();5const ViewBase = path.join(__dirname, 'views');7export const directories = {8 __dirname,9 ViewBase,10 PagesBase: path.join(ViewBase, 'pages'),11 Layout: path.join(ViewBase, 'layout.pug')12};
๐ Quick Start
1. Create Express app
1import express from 'express';2import { Attach } from 'hbh-deve';4const app = express();6await Attach(app); // Loads all views + routes automatically8app.listen(3000, () => {9 console.log('โ
Running at http://localhost:3000');10});
2. Add views
โ views/pages/index.pug
1h1 Welcome to my app!2p Current time: {{ new Date().toLocaleTimeString() }}
โ views/pages/blog/[slug].md
1---2title: My Blog Post3nolayout: false4---6# Hello {{ params.slug }}8This is a markdown page.
โ views/pages/user/[id].js
1export default async ({ params }) => {2 return {3 title: `User: ${params.id}`,4 html: `<p>Hello, <strong>${params.id}</strong></p>`5 };6};8export const middlewares = [9 (req, res, next) => {10 console.log('๐ User route hit');11 next();12 }13];
๐น Layouts
Supports optional layouts (layout.pug) with automatic injection of default content and live reload. The behavior adapts based on what the user layout already contains.
1๏ธโฃ Default Layout Injection
Given a layout like:
1doctype html2html3 head4 title My App5 body6 h1 Header from layout
The engine automatically injects:
- Live reload script (always)
- Default content block if
.page!= pageis missing - Default scripts block if
.scripts!= scriptsis missing
Rendered final Pug:
1doctype html2html3 head4 title My App5 body6 h1 Header from layout8 // โ
Injected automatically:9 script.10 const es = new EventSource('/__reload');11 es.onmessage = (e) => {12 if (e.data === 'reload') location.reload();13 };15 if page16 .page!= page18 if scripts19 .scripts!= scripts
User-defined blocks are respected โ duplicates are never injected.
2๏ธโฃ Per-View Layout Toggle
You can disable the layout per view if needed:
| View Type | How to disable layout |
|---|---|
.pug |
Add //- nolayout at the top |
.md |
Add nolayout: true in frontmatter |
.js |
Return { nolayout: true } from the async view function |
3๏ธโฃ Injection Flow
1 โโโโโโโโโโโโโโโโโโโโโโโ2 โ Load user layout.pug โ3 โโโโโโโโโโโฌโโโโโโโโโโโโ4 โ5 โผ6 โโโโโโโโโโโโโโโโโโโโโ7 โ Strip comments โ8 โโโโโโโโโโโฌโโโโโโโโโโ9 โ10 โผ11 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ12 โ Build injection string โ13 โ (force reload + default โ14 โ content/scripts if missing) โ15 โโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโ16 โ โ17 โ โผ18 โ โโโโโโโโโโโโโโโโโ19 โ โ Inject into โ20 โ โ <body> tag โ21 โ โ maintain โ22 โ โ indentation โ23 โ โโโโโโโฌโโโโโโโโโโ24 โผ โ25 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ26 โ Compile final Pug โ27 โ Render with locals โ28 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
4๏ธโฃ Fallback
If layout.pug does not exist:
- A default layout with all blocks and live reload is used.
- Ensures the page always renders without breaking.
5๏ธโฃ Example Usage in a Pug Page
views/pages/index.pug:
1h1 Welcome2p Current time: {{ new Date().toLocaleTimeString() }}
Behavior:
-
If
layout.pugexists:h1 Welcomeremains intact.page!= pageis injected if missing- Live reload script is added automatically
-
If
layout.pugis missing:- Page is rendered with the default layout
.page!= pageand live reload script are included automatically
6๏ธโฃ Automatic Injection Overview
| Injection Type | When It Happens | Notes |
|---|---|---|
| Live reload script | Always | Injected in every page rendered |
.page!= page block |
Only if missing in user layout.pug |
Ensures content is displayed without duplication |
.scripts!= scripts block |
Only if missing in user layout.pug |
Scripts section is added if not already present |
| User-defined blocks | Never overwritten | Preserves whatever user has already defined |
โ This table helps you quickly see what the engine automatically injects versus what it preserves.
๐ง Supported View Types
| Format | Features |
|---|---|
.pug |
Pug syntax, supports layout.pug, variable injection |
.html |
HTML with {{ variable }} interpolation |
.md |
Markdown with frontmatter, supports nolayout |
.js |
Async view functions, return { html, title, ... }, can export middlewares[] |
๐งฉ Routing Patterns
| File Name | Route Path |
|---|---|
index.pug |
/ |
about.html |
/about |
user/[id].js |
/user/:id |
blog/[[lang]].md |
/blog/:lang? |
docs/[...slug].md |
/docs/* |
api/[[...catch]].js |
/api/*? |
๐ Live Reload
- Automatically watches
views/and reloads browser - Injects
<script>tag automatically in rendered pages - No browser extension required
- Works with
.pug,.html,.md, and.jsviews
๐งช Template Interpolation
You can use {{ variable }} inside .html, .pug, and .md.
1<h1>Hello, {{ query.name }}</h1>2<p>Time: {{ new Date().toLocaleTimeString() }}</p>
Certain blocks like
<script>and comments are excluded from interpolation for safety.
โ Error Overlay
When rendering fails (in dev), a browser overlay will appear:
1โ ๏ธ SSR Rendering Error3File: /views/pages/user/[id].js5Error: Unexpected token export
๐ Disabling Layout
Layout is applied automatically, but you can disable it per view:
| View Type | How to disable layout |
|---|---|
.pug |
Add //- nolayout at top |
.md |
Add in frontmatter: nolayout: true |
.js |
Return { nolayout: true } or include in frontmatter |
๐ API Reference
Attach(app: Express, options?)
- Loads routes from
views/pages/ - Applies view rendering engine
- Enables live reload via SSE
- Supports optional config overrides
๐ฆ Scripts
Add this in your package.json:
1"scripts": {2 "dev": "node index.js"3}
Then run:
1npm run dev
๐ Security Notes
- Sandboxed parser for
.htmland.md - Blocks unsafe expressions like
eval,Function,window, etc.
๐ License
โ๏ธ Author
HBH Made with โค๏ธ for Express.js developers