Color mode

Discovery Recipes

Riebeckite does not have a separate "homepage framework". A homepage and the pages that help readers browse a site are built by combining the Plugins that already exist: their Page Types, their Markdown blocks, and the navigation plugin.

This guide collects recipes for the common discovery routes — featured content, recent posts, an all-posts index, tags, folders, series, and archive — and names the Plugin and option each one uses. Every recipe works with the public Plugin API and Core config; none of them needs a new Core feature.

One rule to remember

Enabling a Plugin registers its pages, but it does not add a link to the Header or Footer. Add the link yourself in navigation({ items }):

ts
navigation({
  secondary: [
    { label: "Posts", href: "/posts" },
    { label: "Tags", href: "/tags" },
  ],
})

Routes are configuration, not constants. The tags index, series list, and archive each have a base path option, so the example paths below are defaults, not fixed values. See Customizing your site for the Header/Footer model.

What the starter already gives you

The starter preset already registers the pieces most sites need:

  • Recent posts on the generated homepage (from recent-posts).
  • Tags and folders listing pages from taxonomy (/tags and /folders by default).
  • Series list and landing pages from series (/series by default).
  • Breadcrumbs in the article header from breadcrumbs.

query, dataview, archive, and folder-pages are available as packages but are not registered by starter. Add the Plugin when a recipe needs it; see Presets for what each preset includes.

Recent posts

This one is on by default. The generated app/routes/index.tsx collects the latest posts and renders them after the homepage body:

tsx
import { RecentPosts, getRecentPosts } from "@riebeckite/plugin-recent-posts";
import { content } from "../content";
 
const recentPosts = getRecentPosts({ manifest: await content.getManifest() });

getRecentPosts() defaults to 5 posts and reads manifest.discoverableEntries, so unlisted, draft, and scheduled notes are excluded along with the index note. It sorts by date (falling back to created). To change the number or where the list appears, edit the route; passing limit overrides the default. Notes without a parseable date are dropped. There is no recent-posts Markdown block — it is a component the site places.

"Featured" is not a Core concept: it is a frontmatter flag or a tag that you filter on. Add notes with featured: true:

yaml
---
title: A hand-picked article
featured: true
---

Then render them on the homepage with a query block. Install and register @riebeckite/plugin-query first (it is not in starter):

ts
plugins: [queryPlugin()],
md
```query
filter:
  frontmatter:
    featured: true
sort:
  field: date
  order: desc
limit: 3
format: list
```

A tag works the same way and is easier to apply from Obsidian. Tag notes with featured, then filter on the tag:

md
```query
filter:
  tags:
    any: [featured]
sort:
  field: date
  order: desc
limit: 3
format: list
```

With @riebeckite/plugin-dataview you can express the same thing in Dataview syntax:

md
```dataview
LIST file.date
FROM #featured
SORT file.date desc
LIMIT 3
```

Both query and dataview run at build time and add no client JavaScript. Note that links they produce are not added to the content graph, so backlinks are not created for them.

An all-posts index

An all-posts page is a query with no filter. Give it its own note, for example content/posts.md, and link it from the navigation plugin:

md
---
title: All posts
---
 
# All posts
 
```query
sort:
  field: date
  order: desc
format: list
excludeSelf: true
```

excludeSelf: true keeps the posts note out of its own list. Use format: table with columns to show a table instead:

md
```query
sort:
  field: date
  order: desc
format: table
columns: [title, date, tags]
excludeSelf: true
```

If you prefer a monthly browse instead of a flat list, use the archive recipe below.

Tags

taxonomy generates a tags index at tagsBasePath (default /tags) and one page per tag at tagsBasePath/<slug>. It is already registered in starter. Add it to the navigation so readers can find it:

ts
navigation({ secondary: [{ label: "Tags", href: "/tags" }] })

Change the prefix when /tags clashes with your content:

ts
taxonomy({ tagsBasePath: "/topics" }),

Per-tag RSS, Atom, and JSON feeds are emitted alongside each term page. Use folders: false if you only want tags.

Folders

There are two folder-shaped routes, and they can be used together:

  • taxonomy lists notes grouped by folder at foldersBasePath (default /folders), with one page per folder.
  • folder-pages turns each folder into a landing page at that folder's path (for example /notes/), and collapses a folder's README.md or index.md into that landing page.

For a folder listing, add the taxonomy path to the navigation:

ts
navigation({ secondary: [{ label: "Folders", href: "/folders" }] })

Change the prefix with taxonomy({ foldersBasePath: "/directories" }). Because folder-pages changes where README.md and index.md resolve, enable it only when you want a landing page per folder; it is not registered by starter.

Series

A series is a set of notes that share a series frontmatter key. series is in starter and already appends previous/next navigation to each part. It also publishes a list page at basePath (default /series) and one landing page per series at basePath/<name>:

yaml
---
title: Part 1
series: Build a thing
series_order: 1
---

Link the list from the navigation plugin:

ts
navigation({ secondary: [{ label: "Series", href: "/series" }] })

Change the prefix with series({ basePath: "/guides" }), or set basePath: "" to turn the generated pages off and render them yourself from the exported buildSeriesIndex() / renderSeriesIndex() helpers.

Archive

archive publishes one listing page per month at basePath/<yyyy>/<mm> (default /archive) with pagination. Add the Plugin:

ts
plugins: [archive()],

Then link the base path:

ts
navigation({ secondary: [{ label: "Archive", href: "/archive" }] })

Use archive({ basePath: "/history", pageSize: 20 }) to change the prefix or page size. pageSize: 0 keeps a single page per month.

Putting it together

A blog-style homepage often combines a few of these:

  1. A lead paragraph in content/index.md.
  2. A query block for featured notes.
  3. <RecentPosts /> from the generated homepage route.
  4. Navigation links to /tags, /series, and /archive.

A docs-style site leans on folder-pages for section landings and on series for ordered guides. A vault with few nested folders may skip folder pages entirely. Pick the pieces that match the content you have.

What this does not need

These recipes deliberately avoid new abstractions. There is no homepage framework, no Featured API, and no Core discovery registry: featured content, all-posts indexes, and archive pages are all combinations of existing Plugins, Markdown blocks, and the navigation plugin.

Where to look next

History

1 changesCollapseExpand
1 + # Discovery Recipes
2 +
3 + Riebeckite does not have a separate "homepage framework". A homepage and the
4 + pages that help readers browse a site are built by combining the Plugins that
5 + already exist: their Page Types, their Markdown blocks, and the `navigation`
6 + plugin.
7 +
8 + This guide collects recipes for the common discovery routes — featured content,
9 + recent posts, an all-posts index, tags, folders, series, and archive — and names
10 + the Plugin and option each one uses. Every recipe works with the public Plugin
11 + API and Core config; none of them needs a new Core feature.
12 +
13 + ## One rule to remember
14 +
15 + Enabling a Plugin registers its pages, but it does **not** add a link to the
16 + Header or Footer. Add the link yourself in `navigation({ items })`:
17 +
18 + ```ts
19 + navigation({
20 + secondary: [
21 + { label: "Posts", href: "/posts" },
22 + { label: "Tags", href: "/tags" },
23 + ],
24 + })
25 + ```
26 +
27 + Routes are configuration, not constants. The tags index, series list, and
28 + archive each have a base path option, so the example paths below are defaults,
29 + not fixed values. See [Customizing your site](./customizing-your-site.en.md) for the
30 + Header/Footer model.
31 +
32 + ## What the starter already gives you
33 +
34 + The `starter` preset already registers the pieces most sites need:
35 +
36 + - **Recent posts** on the generated homepage (from `recent-posts`).
37 + - **Tags and folders** listing pages from `taxonomy` (`/tags` and `/folders` by
38 + default).
39 + - **Series** list and landing pages from `series` (`/series` by default).
40 + - **Breadcrumbs** in the article header from `breadcrumbs`.
41 +
42 + `query`, `dataview`, `archive`, and `folder-pages` are available as packages but
43 + are not registered by `starter`. Add the Plugin when a recipe needs it; see
44 + [Presets](../getting-started/presets.en.md) for what each preset includes.
45 +
46 + ## Recent posts
47 +
48 + This one is on by default. The generated `app/routes/index.tsx` collects the
49 + latest posts and renders them after the homepage body:
50 +
51 + ```tsx
52 + import { RecentPosts, getRecentPosts } from "@riebeckite/plugin-recent-posts";
53 + import { content } from "../content";
54 +
55 + const recentPosts = getRecentPosts({ manifest: await content.getManifest() });
56 + ```
57 +
58 + `getRecentPosts()` defaults to 5 posts and reads `manifest.discoverableEntries`,
59 + so `unlisted`, `draft`, and scheduled notes are excluded along with the `index`
60 + note. It sorts by `date` (falling back to `created`). To change the number or
61 + where the list appears, edit the route; passing `limit` overrides the default.
62 + Notes without a parseable date are dropped. There is no `recent-posts` Markdown
63 + block — it is a component the site places.
64 +
65 + ## Featured content
66 +
67 + "Featured" is not a Core concept: it is a frontmatter flag or a tag that you
68 + filter on. Add notes with `featured: true`:
69 +
70 + ```yaml
71 + ---
72 + title: A hand-picked article
73 + featured: true
74 + ---
75 + ```
76 +
77 + Then render them on the homepage with a `query` block. Install and register
78 + `@riebeckite/plugin-query` first (it is not in `starter`):
79 +
80 + ```ts
81 + plugins: [queryPlugin()],
82 + ```
83 +
84 + ````md
85 + ```query
86 + filter:
87 + frontmatter:
88 + featured: true
89 + sort:
90 + field: date
91 + order: desc
92 + limit: 3
93 + format: list
94 + ```
95 + ````
96 +
97 + A tag works the same way and is easier to apply from Obsidian. Tag notes with
98 + `featured`, then filter on the tag:
99 +
100 + ````md
101 + ```query
102 + filter:
103 + tags:
104 + any: [featured]
105 + sort:
106 + field: date
107 + order: desc
108 + limit: 3
109 + format: list
110 + ```
111 + ````
112 +
113 + With `@riebeckite/plugin-dataview` you can express the same thing in Dataview
114 + syntax:
115 +
116 + ````md
117 + ```dataview
118 + LIST file.date
119 + FROM #featured
120 + SORT file.date desc
121 + LIMIT 3
122 + ```
123 + ````
124 +
125 + Both `query` and `dataview` run at build time and add no client JavaScript. Note
126 + that links they produce are not added to the content graph, so backlinks are not
127 + created for them.
128 +
129 + ## An all-posts index
130 +
131 + An all-posts page is a `query` with no filter. Give it its own note, for example
132 + `content/posts.md`, and link it from the `navigation` plugin:
133 +
134 + ````md
135 + ---
136 + title: All posts
137 + ---
138 +
139 + # All posts
140 +
141 + ```query
142 + sort:
143 + field: date
144 + order: desc
145 + format: list
146 + excludeSelf: true
147 + ```
148 + ````
149 +
150 + `excludeSelf: true` keeps the `posts` note out of its own list. Use
151 + `format: table` with `columns` to show a table instead:
152 +
153 + ````md
154 + ```query
155 + sort:
156 + field: date
157 + order: desc
158 + format: table
159 + columns: [title, date, tags]
160 + excludeSelf: true
161 + ```
162 + ````
163 +
164 + If you prefer a monthly browse instead of a flat list, use the archive recipe
165 + below.
166 +
167 + ## Tags
168 +
169 + `taxonomy` generates a tags index at `tagsBasePath` (default `/tags`) and one
170 + page per tag at `tagsBasePath/<slug>`. It is already registered in `starter`.
171 + Add it to the navigation so readers can find it:
172 +
173 + ```ts
174 + navigation({ secondary: [{ label: "Tags", href: "/tags" }] })
175 + ```
176 +
177 + Change the prefix when `/tags` clashes with your content:
178 +
179 + ```ts
180 + taxonomy({ tagsBasePath: "/topics" }),
181 + ```
182 +
183 + Per-tag RSS, Atom, and JSON feeds are emitted alongside each term page. Use
184 + `folders: false` if you only want tags.
185 +
186 + ## Folders
187 +
188 + There are two folder-shaped routes, and they can be used together:
189 +
190 + - `taxonomy` lists notes grouped by folder at `foldersBasePath` (default
191 + `/folders`), with one page per folder.
192 + - `folder-pages` turns each folder into a landing page at that folder's path
193 + (for example `/notes/`), and collapses a folder's `README.md` or `index.md`
194 + into that landing page.
195 +
196 + For a folder listing, add the taxonomy path to the navigation:
197 +
198 + ```ts
199 + navigation({ secondary: [{ label: "Folders", href: "/folders" }] })
200 + ```
201 +
202 + Change the prefix with `taxonomy({ foldersBasePath: "/directories" })`. Because
203 + `folder-pages` changes where `README.md` and `index.md` resolve, enable it only
204 + when you want a landing page per folder; it is not registered by `starter`.
205 +
206 + ## Series
207 +
208 + A series is a set of notes that share a `series` frontmatter key. `series` is in
209 + `starter` and already appends previous/next navigation to each part. It also
210 + publishes a list page at `basePath` (default `/series`) and one landing page per
211 + series at `basePath/<name>`:
212 +
213 + ```yaml
214 + ---
215 + title: Part 1
216 + series: Build a thing
217 + series_order: 1
218 + ---
219 + ```
220 +
221 + Link the list from the navigation plugin:
222 +
223 + ```ts
224 + navigation({ secondary: [{ label: "Series", href: "/series" }] })
225 + ```
226 +
227 + Change the prefix with `series({ basePath: "/guides" })`, or set `basePath: ""`
228 + to turn the generated pages off and render them yourself from the exported
229 + `buildSeriesIndex()` / `renderSeriesIndex()` helpers.
230 +
231 + ## Archive
232 +
233 + `archive` publishes one listing page per month at `basePath/<yyyy>/<mm>` (default
234 + `/archive`) with pagination. Add the Plugin:
235 +
236 + ```ts
237 + plugins: [archive()],
238 + ```
239 +
240 + Then link the base path:
241 +
242 + ```ts
243 + navigation({ secondary: [{ label: "Archive", href: "/archive" }] })
244 + ```
245 +
246 + Use `archive({ basePath: "/history", pageSize: 20 })` to change the prefix or
247 + page size. `pageSize: 0` keeps a single page per month.
248 +
249 + ## Putting it together
250 +
251 + A blog-style homepage often combines a few of these:
252 +
253 + 1. A lead paragraph in `content/index.md`.
254 + 2. A `query` block for featured notes.
255 + 3. `<RecentPosts />` from the generated homepage route.
256 + 4. Navigation links to `/tags`, `/series`, and `/archive`.
257 +
258 + A docs-style site leans on `folder-pages` for section landings and on `series`
259 + for ordered guides. A vault with few nested folders may skip folder pages
260 + entirely. Pick the pieces that match the content you have.
261 +
262 + ## What this does not need
263 +
264 + These recipes deliberately avoid new abstractions. There is no homepage
265 + framework, no Featured API, and no Core discovery registry: featured content,
266 + all-posts indexes, and archive pages are all combinations of existing Plugins,
267 + Markdown blocks, and the `navigation` plugin.
268 +
269 + ## Where to look next
270 +
271 + - [Customizing your site](./customizing-your-site.en.md) — the Header/Footer and body-slot model
272 + - [Presets](../getting-started/presets.en.md) — which Plugins each preset registers
273 + - [Plugins](../plugins/README.en.md) — the Plugin catalog
274 + - [Plugin API](../reference/plugin-api.en.md) — Page Types, body slots, and CSS hooks
275 +