aRustyDev / aRustyDev/mdbook-htmx
docs(adr): ADR-0002: Dual Output Mode (Full Pages + Fragments)
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
# ADR-0002: Dual Output Mode (Full Pages + Fragments)
## Status
Accepted
## Context
HTMX-based applications require two types of responses:
1. **Full page** - Initial load or non-HTMX clients (includes ``, ``, layout)
2. **Fragment** - HTMX requests with `HX-Request: true` header (content only)
The backend must decide how to generate these outputs.
## Decision Drivers
1. **Server Simplicity** - Minimize runtime logic in serving layer
2. **Cache Efficiency** - Static files can be cached at CDN edge
3. **Flexibility** - Support different deployment models
4. **Build Performance** - Avoid excessive output duplication
## Options Considered
### Option A: Single Output, Runtime Detection
Generate only full pages; server strips layout at runtime.
```
book/htmx/
└── pages/
└── chapter-1.html # Full page only
```
Server uses HTML parsing (lol_html, cheerio) to extract `` content.
**Pros:**
- Smaller build output
- Single source of truth
**Cons:**
- Runtime overhead on every HTMX request
- Requires HTML parser in server
- Complex edge caching (Vary header needed)
### Option B: Fragment-Only Output
Generate only fragments; server wraps in layout at runtime.
```
book/htmx/
└── fragments/
└── chapter-1.html # Content only
```
Server composes full pages by injecting fragment into layout.
**Pros:**
- Smallest build output
- DRY templates
**Cons:**
- Full page requests require runtime composition
- Layout changes require server restart
- Less cacheable
### Option C: Dual Output (Recommended)
Generate both full pages and fragments at build time.
```
book/htmx/
├── pages/
│ └── chapter-1.html # Full page (layout + content)
└── fragments/
└── chapter-1.html # Content only
```
Server selects file based on `HX-Request` header.
**Pros:**
- Zero runtime overhead (file selection only)
- Full CDN cacheability
- Works with static file servers
- Progressive enhancement (works without JS)
**Cons:**
- ~2x output size
- Build-time layout changes require rebuild
### Option D: Configurable Mode
Let user choose via config:
```toml
[output.htmx]
output-mode = "full" | "fragments" | "both"
```
**Pros:**
- Maximum flexibility
- Users optimize for their deployment
**Cons:**
- More complex documentation
- Testing burden
## Decision
**Implement dual output as default, with configurable `output-mode`.**
Default: `output-mode = "both"`
Rationale:
1. Zero runtime overhead is critical for edge deployments (Cloudflare Workers)
2. Static file selection (`HX-Request` → fragments/, else → pages/) is trivial
3. Storage is cheap; compute at edge is not
4. Progressive enhancement requires full pages for initial load
5. Config option satisfies users with specific constraints
## Implementation
```rust
enum OutputMode {
Full, // Only pages/
Fragments, // Only fragments/
Both, // Both directories (default)
}
fn render_chapter(&self, chapter: &Chapter, mode: OutputMode) {
match mode {
OutputMode::Full | OutputMode::Both => {
write_file(pages_dir.join(&chapter.path), render_full(chapter));
}
OutputMode::Fragments | OutputMode::Both => {
write_file(fragments_dir.join(&chapter.path), render_fragment(chapter));
}
}
}
```
## Consequences
### Positive
- Simple server logic (file routing only)
- CDN-friendly (static files, no Vary complexity)
- Works with any static file server
- Progressive enhancement out of the box
### Negative
- Larger build output (~2x for `both` mode)
- Layout changes require full rebuild
### Mitigation
- Document `output-mode = "fragments"` for dynamic server deployments
- Implement incremental builds (only changed chapters)
## References
- [HTMX Hypermedia Patterns](https://htmx.org/essays/hypermedia-driven-applications/)
- [Cloudflare Workers Static Assets](https://developers.cloudflare.com/workers/configuration/sites/)
Contributor guide
Assessment
This issue has not been assessed yet.