aRustyDev / aRustyDev/mdbook-htmx

docs(adr): ADR-0002: Dual Output Mode (Full Pages + Fragments)

Open
#10 0 comments 0 reactions 1 assignee Claimed by @aRustyDev View on GitHub
documentation
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.