bserver serves different content based on the request hostname. Each domain gets its own document root directory:
/var/www/
├── example.com/
│ ├── index.yaml
│ ├── navlinks.yaml
│ └── style.yaml
├── blog.example.com/
│ ├── index.yaml
│ └── navlinks.yaml
└── default/
└── index.yaml
When a request comes in for example.com, bserver looks for content in
/var/www/example.com/. The default/ directory serves as the fallback
for any host that doesn't have a dedicated directory.
bserver supports automatic HTTPS with Let's Encrypt certificates via the
golang.org/x/crypto/acme/autocert package. When HTTPS is configured,
certificates are automatically obtained and renewed for the domains being
served.
bserver recognizes standard HTML5 tags. When a YAML key matches a known tag name, it is rendered directly as that HTML element rather than being treated as a name to resolve. For custom elements or non-standard tags, use a format definition instead:
^my-component:
tag: my-component
main:
- my-component: "Content inside custom element"
Renders: <my-component>Content inside custom element</my-component>
Document: html, head, body, title, meta, link, style,
script
Content: div, span, p, br, hr, h1-h6, a, img,
pre, code, blockquote
Text: strong, em, b, i, u, small
Lists: ul, ol, li
Tables: table, tr, td, th, thead, tbody
Forms: form, input, button, textarea, select, option,
label, fieldset, legend
Semantic: header, footer, nav, main, section, article,
aside, details, summary
Media: video, audio, source, canvas
Other: embed, area, base, col, track, wbr
These tags are self-closing and never produce a closing tag:
meta, link, br, hr, img, input, source, area, base,
col, embed, track, wbr
The style: name is special — its content is rendered as CSS rather than
HTML elements:
style:
body:
font-family: sans-serif
margin: 0
padding: 0
.header:
background-color: "#2c3e50"
color: white
padding: 1rem
.content p:
line-height: 1.6
max-width: 800px
Renders:
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 0;
}
.header {
background-color: #2c3e50;
color: white;
padding: 1rem;
}
.content p {
line-height: 1.6;
max-width: 800px;
}
</style>
Each top-level key is a CSS selector, and its map entries become CSS property-value pairs.
Component files can add styles using the +style merge prefix:
+style:
.my-component:
border: 1px solid "#ccc"
border-radius: 4px
This adds your CSS rules to whatever styles already exist.
When a request comes in for a directory path like /service/, bserver
looks for content in this order:
service/index.yaml (or index.md, index.php, etc.)service/service.yaml (directory name matches
file name)This allows clean URL patterns:
mysite.com/
├── service/
│ └── service.yaml # Served at /service/
├── products/
│ └── products.yaml # Served at /products/
└── index.yaml # Served at /
The request URI is computed by stripping the file extension and handling
the directory-name matching: service/service.yaml becomes /service.
Any .md file in the document root is automatically rendered as a full HTML
page. The markdown content is:
main content definitionThis means markdown files automatically get:
Inline HTML in markdown is preserved (not escaped), so you can mix markdown with raw HTML tags, images, iframes, etc.
When bserver encounters a name reference, it searches for a definition:
name.yaml in the request directorymaxParentLevels above the document root (default: 1
level above)For example, with document root /var/www/mysite.com/ and a request for
/service/:
Search order:
1. /var/www/mysite.com/service/name.yaml
2. /var/www/mysite.com/name.yaml
3. /var/www/name.yaml (1 level above docRoot)
4. Stop (ceiling reached)
This cascading search is why shared definitions (like html.yaml,
navbar.yaml) in the content root directory work for all sites — they're
found when the search walks up from the site directory.
Names can also resolve to .md files. If name.yaml isn't found but
name.md exists, the markdown file is read, converted to HTML, and used
as the definition. This allows mixing YAML structure with markdown content
seamlessly.
bserver uses a two-pass rendering system:
Walks the entire name tree starting from html, loading all referenced YAML
files and processing + merges. This ensures that features like +style
from component files (e.g., Bootstrap) are applied before rendering begins.
Generates HTML from the fully-resolved definitions. At this point, all names are loaded, all merges are applied, and all formats are registered.
This two-pass approach prevents ordering issues. For example, if navbar.yaml
adds +headlink entries for Bootstrap CSS, those entries are available in
the head section even though the navbar is defined after the head in the body
structure.
Format definitions (^name) from the page's own YAML file are preserved
across both passes. If a component file loaded during resolution defines the
same format, the page-level definition takes precedence. This allows
individual pages to customize rendering.
bserver tracks which names are currently being resolved/rendered. If a name references itself (directly or indirectly), the cycle is broken with an HTML comment:
<!-- circular reference: "myname" -->
The maximum nesting depth is 50 levels to prevent runaway recursion.
If a name can't be resolved (no YAML file found, no definition loaded), bserver renders a visible error indicator:
<div style="border:2px dashed red;padding:8px;margin:4px;color:red;">
Undefined name: <strong>myname</strong>
</div>
This makes missing definitions immediately obvious during development.
Add ?debug to any URL to enable debug HTML comments throughout the rendered
output:
<!-- resolve "html" from /path/to/html.yaml -->
<!-- ^html: tag="html" contents="" -->
<!-- key "head" -->
<!-- resolve "head" from /path/to/head.yaml -->
These comments trace the full resolution and rendering process, showing which files are loaded, which formats are applied, and how content flows through the system.
bserver preserves the order of YAML map keys throughout parsing and rendering. Standard Go maps are unordered, but bserver uses a custom OrderedMap implementation to ensure that:
This is important because YAML maps are technically unordered, but in practice users expect their defined order to be preserved.
bserver derives the URL path from the filesystem path:
| Filesystem Path | Request URI |
|---|---|
mysite.com/index.yaml |
/ |
mysite.com/about.yaml |
/about |
mysite.com/service/service.yaml |
/service |
mysite.com/blog/post.yaml |
/blog/post |
This computed URI is used for the REQUEST_URI environment variable in
scripts, enabling features like active-page navigation highlighting.
^ syntax