# mdbook-content-loader
**Zero-runtime-fetch content collections for mdBook**
Injects your `content-collections.json` (generated by
[`mdbook-content-collections`](https://crates.io/crates/mdbook-content-collections))
directly into every HTML page as `window.CONTENT_COLLECTIONS` — **no network
request, no path hacks, works offline**.
By default, the data is injected on your landing page (`index.md`), which is
usually where your “Latest posts” or home layout lives. You can optionally
enable the old behavior and inject it on _every_ page via a config flag.
This gives you true **Astro/Zola-style content collections** inside mdBook:
instant, reliable access to posts, blog entries, notes, changelogs — anywhere in
your theme.
---
## Why this exists (and why you want it)
The original `mdbook-content-collections` example used
`fetch('content-collections.json')` in the browser.
That works… until it doesn’t:
| Extra HTTP request | Yes | No |
| Broken in `file://` / offline mode | Yes | Works perfectly |
| Fragile URL construction | Yes | No path logic needed |
| Flash of empty content | Yes | Instant |
| Fails silently on 404 | Yes | Build fails early |
| Works on GitHub Pages / custom domains | Sometimes | Always |
**This crate fixes all of that — permanently.**
---
## What you get
A single global, available **immediately** on every page:
```html
<script>
window.CONTENT_COLLECTIONS = { ...ready to use... }
</script>
```
```js
window.CONTENT_COLLECTIONS = {
entries: [
/* all published posts, sorted newest first */
],
collections: {
posts: [
/* fallback collection */
],
blog: [
/* frontmatter: collection: blog */
],
notes: [
/* ... */
],
// ... any collection name you use
},
generated_at: "2025-11-28T10:00:00Z",
};
```
Each entry has the full shape from `mdbook-content-collections`:
```ts
{
path: "blog/my-post.md",
title: "My Great Post",
date: "2025-11-27T00:00:00Z",
author?: string,
description?: string,
collection?: string,
tags: string[],
draft: false,
preview_html: "<p>First paragraph <strong>rendered</strong>...</p>"
}
```
- Drafts are filtered out automatically
- Everything is sorted newest -> oldest
- No runtime parsing, fetching, or error handling needed
## Installation
```bash
cargo install mdbook-content-loader
# If not installed, install mdbook-content-collections
# cargo install mdbook-content-collections
```
**Setup**
Add the following to your `book.toml`:
```toml
[preprocessor.content-collections]
# Generates content-collections.json
[preprocessor.content-loader]
command = "mdbook-content-loader"
after = ["content-collections"] # make sure JSON is generated first
# Behavior:
# - default: inject window.CONTENT_COLLECTIONS only into index.md
# - set inject_all = true to inject into every chapter
# inject_all = true
```
The `after` key ensures the JSON exists before this loader runs, and
`inject_all` is an optional boolean that restores the old “every page” behavior.
mdBook exposes this table under `[preprocessor.content-loader]` to the
preprocessor via `PreprocessorContext`, which is where this flag is read.
## Usage in your theme
Drop this anywhere in `theme/index.hbs` (or any `.hbs` file):
```html
<div id="latest-posts"></div>
<script>
if (!window.CONTENT_COLLECTIONS) {
console.warn("mdbook-content-loader: data not loaded");
} else {
const posts =
window.CONTENT_COLLECTIONS.collections.blog ||
window.CONTENT_COLLECTIONS.collections.posts ||
[];
const list = document.getElementById("latest-posts");
const ul = document.createElement("ul");
posts.slice(0, 5).forEach((post) => {
const li = document.createElement("li");
li.innerHTML = `
<h3><a href="$$ {post.path.replace(/\.md $$/, '.html')}">${post.title}</a></h3>
${post.date ? `<time>${new Date(post.date).toISOString().slice(0, 10)}</time>` : ""}
<div class="preview">${post.preview_html}</div>
`;
ul.appendChild(li);
});
list.appendChild(ul);
}
</script>
```
Want a sidebar? Tag cloud? Search index? Related posts? Just read from
`window.CONTENT_COLLECTIONS`.
## Common patterns
```js
// Latest 5 blog posts
window.CONTENT_COLLECTIONS.collections.blog?.slice(0, 5);
// All posts (any collection)
window.CONTENT_COLLECTIONS.entries;
// Posts with tag "rust"
window.CONTENT_COLLECTIONS.entries.filter((p) => p.tags.includes("rust"));
```
## Data Shape
Same as `mdbook-content-collections`, plus:
- `collections`: grouped + sorted by collection name
- `generated_at`: when the data was build
- All `draft: true` entries removed
See:
[mdbook-content-collections](https://crates.io/crates/mdbook-content-collections)
### License
Apache 2.0