Color mode

Filesystem roots and external vaults

Riebeckite keeps the site application and its source material separate. The following names describe different directories and must not be used interchangeably:

Diagram source
text
flowchart TD
    App["appRoot<br/>Site application"]
    Config["configRoot<br/>Where the config lives"]
    Content["contentRoot<br/>Content / vault location"]
 
    App -->|"default"| Config
    App -->|"resolve content.directory"| Content
Name Responsibility Default / resolution base
appRoot The HonoX/Vite application: app/, public/, routes, generated styles, and build output configuration Vite's root
configRoot Directory containing riebeckite.config.ts, .js, or .mjs appRoot
contentRoot Absolute filesystem root for the configured content directory or Obsidian vault path.resolve(appRoot, content.directory)

configRoot determines where the config module is imported from. It does not change the base for a relative content.directory: that base is always appRoot. The integration resolves all three roots before running content or plugins, and CLI commands reuse that result. Consequently, execution from a nested directory, CI working directory, or editor task does not change which vault is read.

appRoot

appRoot is the base directory of the site application. For a layout such as:

text
site/
├─ app/
├─ public/
├─ package.json
├─ vite.config.ts
└─ riebeckite.config.ts

appRoot is normally site/. It is the base for app/, public/, routes, generated styles, and build configuration.

configRoot

configRoot is the base used to find riebeckite.config.ts, riebeckite.config.js, or riebeckite.config.mjs. It is normally the same as appRoot:

text
appRoot
   └─ riebeckite.config.ts

Change it only when a repository layout deliberately places the config elsewhere. Changing configRoot does not change the base for content.directory.

contentRoot

contentRoot is where Markdown and assets are actually read from. A relative content.directory is always resolved against appRoot:

ts
content: {
  directory: "../vault",
}

so:

text
contentRoot
  = path.resolve(appRoot, "../vault")
Diagram source
text
flowchart LR
    App["appRoot<br/>workspace/site"]
    Directory["content.directory<br/>../vault"]
    Root["contentRoot<br/>workspace/vault"]
 
    App --> Directory
    Directory --> Root

The base is not process.cwd(). Running the CLI from another directory therefore still reads the same vault as long as it resolves to the same site application.

Keep an Obsidian vault outside the site when it is used independently by Obsidian, shared by multiple site applications, or stored in another Git repository:

text
workspace/
├─ site/
│  ├─ package.json
│  ├─ vite.config.ts
│  ├─ riebeckite.config.ts
│  ├─ app/
│  └─ public/
└─ vault/
   ├─ index.md
   ├─ notes/
   ├─ attachments/
   └─ media/
Diagram source
text
flowchart LR
    Site["site/<br/>HonoX / Vite application"]
    Config["riebeckite.config.ts"]
    Vault["vault/<br/>Obsidian content"]
 
    Site --> Config
    Config -->|"content.directory = ../vault"| Vault

The site and vault roles stay separate:

text
site/
  → application
 
vault/
  → source content

The vault does not need to be the Vite application root.

With this layout, the site configuration is explicit and portable:

ts
// site/riebeckite.config.ts
import { defineConfig } from "@riebeckite/core";
import { attachment } from "@riebeckite/plugin-attachment";
import { media } from "@riebeckite/plugin-media";
import { obsidianMarkdown } from "@riebeckite/plugin-obsidian-markdown";
 
export default defineConfig({
  site: { title: "My notes" },
  content: {
    directory: "../vault",
    exclude: [".obsidian/**", "Templates/**"],
  },
  plugins: [obsidianMarkdown(), media(), attachment()],
});

In this layout:

text
appRoot
  = workspace/site
 
content.directory
  = ../vault
 
contentRoot
  = workspace/vault

An absolute directory is also valid:

ts
content: {
  directory: "C:/Users/example/Documents/vault",
}

but an absolute path stops working when the location changes between developer machines and CI, so a path relative to the site is normally preferred. Do not derive the value with process.cwd(), and do not make appRoot point to the vault. The vault is source data; Vite's application root must remain the site.

Not depending on process.cwd()

Avoid building the content directory like this:

ts
directory: path.resolve(
  process.cwd(),
  "../vault",
)

The result changes depending on where the CLI is invoked. You also do not need:

text
appRoot = Vault

The vault is source data; appRoot is the site application:

Diagram source
text
flowchart LR
    Vault["Vault<br/>source data"]
    Site["Site<br/>application"]
    Build["Riebeckite"]
 
    Vault --> Build
    Site --> Build
 
    Build --> Output["Generated site"]

Keep this boundary.

Application-side content access

The HonoX integration resolves the content root automatically and exposes the resolved values as framework-owned modules. A site imports them instead of re-reading riebeckite.config.ts:

ts
import { config } from "virtual:riebeckite/config";
import { content } from "virtual:riebeckite/content";

config.content.directory is already absolute, and content is a ready ContentManager bound to it, so there is no second resolution step and no per-site config copy to keep in sync. The riebeckiteVite() plugin resolves these modules. A script that runs outside Vite (for example a Node script started with tsx) can resolve the same values with resolveHonoxConfig from @riebeckite/honox/runtime:

ts
import { fileURLToPath } from "node:url";
import { resolveConfigModule } from "@riebeckite/core";
import { resolveHonoxConfig } from "@riebeckite/honox/runtime";
import * as rawConfigModule from "../riebeckite.config";
 
const appRoot = fileURLToPath(new URL("../", import.meta.url));
export const config = resolveHonoxConfig(resolveConfigModule(rawConfigModule), appRoot);
Diagram source
text
flowchart LR
    Relative["../vault"]
    Resolve["resolve once from appRoot"]
    Absolute["C:/.../vault"]
    Manager["ContentManager"]
 
    Relative --> Resolve
    Resolve --> Absolute
    Absolute --> Manager

If content.source is configured, it replaces the filesystem reader; do not use it as a second reader for the same vault.

Placing the config outside the site

Normally:

text
appRoot
  = configRoot

You can deliberately place riebeckite.config.ts in another directory by passing configRoot to riebeckiteVite().

The roles do not change:

text
appRoot
  → site application
 
configRoot
  → config
 
contentRoot
  → content / vault

In particular, do not change appRoot to point at the vault. A relative content.directory is still resolved against appRoot, not configRoot.

History

1 changesCollapseExpand
1 + ---
2 + title: Filesystem roots and external vaults
3 + sidebar:
4 + label: Filesystem roots and external vaults
5 + order: 30
6 + ---
7 +
8 + # Filesystem roots and external vaults
9 +
10 + Riebeckite keeps the site application and its source material separate. The
11 + following names describe different directories and must not be used
12 + interchangeably:
13 +
14 + ```mermaid
15 + flowchart TD
16 + App["appRoot<br/>Site application"]
17 + Config["configRoot<br/>Where the config lives"]
18 + Content["contentRoot<br/>Content / vault location"]
19 +
20 + App -->|"default"| Config
21 + App -->|"resolve content.directory"| Content
22 + ```
23 +
24 + | Name | Responsibility | Default / resolution base |
25 + | --- | --- | --- |
26 + | `appRoot` | The HonoX/Vite application: `app/`, `public/`, routes, generated styles, and build output configuration | Vite's `root` |
27 + | `configRoot` | Directory containing `riebeckite.config.ts`, `.js`, or `.mjs` | `appRoot` |
28 + | `contentRoot` | Absolute filesystem root for the configured content directory or Obsidian vault | `path.resolve(appRoot, content.directory)` |
29 +
30 + `configRoot` determines where the config module is imported from. It does
31 + **not** change the base for a relative `content.directory`: that base is always
32 + `appRoot`. The integration resolves all three roots before running content or
33 + plugins, and CLI commands reuse that result. Consequently, execution from a
34 + nested directory, CI working directory, or editor task does not change which
35 + vault is read.
36 +
37 + ## appRoot
38 +
39 + `appRoot` is the base directory of the **site application**. For a layout such
40 + as:
41 +
42 + ```text
43 + site/
44 + ├─ app/
45 + ├─ public/
46 + ├─ package.json
47 + ├─ vite.config.ts
48 + └─ riebeckite.config.ts
49 + ```
50 +
51 + `appRoot` is normally `site/`. It is the base for `app/`, `public/`, routes,
52 + generated styles, and build configuration.
53 +
54 + ## configRoot
55 +
56 + `configRoot` is the base used to find `riebeckite.config.ts`,
57 + `riebeckite.config.js`, or `riebeckite.config.mjs`. It is normally the same as
58 + `appRoot`:
59 +
60 + ```text
61 + appRoot
62 + └─ riebeckite.config.ts
63 + ```
64 +
65 + Change it only when a repository layout deliberately places the config
66 + elsewhere. **Changing `configRoot` does not change the base for
67 + `content.directory`.**
68 +
69 + ## contentRoot
70 +
71 + `contentRoot` is where Markdown and assets are actually read from. A relative
72 + `content.directory` is always resolved against `appRoot`:
73 +
74 + ```ts
75 + content: {
76 + directory: "../vault",
77 + }
78 + ```
79 +
80 + so:
81 +
82 + ```text
83 + contentRoot
84 + = path.resolve(appRoot, "../vault")
85 + ```
86 +
87 + ```mermaid
88 + flowchart LR
89 + App["appRoot<br/>workspace/site"]
90 + Directory["content.directory<br/>../vault"]
91 + Root["contentRoot<br/>workspace/vault"]
92 +
93 + App --> Directory
94 + Directory --> Root
95 + ```
96 +
97 + The base is not `process.cwd()`. Running the CLI from another directory
98 + therefore still reads the same vault as long as it resolves to the same site
99 + application.
100 +
101 + ## Recommended layout
102 +
103 + Keep an Obsidian vault outside the site when it is used independently by
104 + Obsidian, shared by multiple site applications, or stored in another Git
105 + repository:
106 +
107 + ```text
108 + workspace/
109 + ├─ site/
110 + │ ├─ package.json
111 + │ ├─ vite.config.ts
112 + │ ├─ riebeckite.config.ts
113 + │ ├─ app/
114 + │ └─ public/
115 + └─ vault/
116 + ├─ index.md
117 + ├─ notes/
118 + ├─ attachments/
119 + └─ media/
120 + ```
121 +
122 + ```mermaid
123 + flowchart LR
124 + Site["site/<br/>HonoX / Vite application"]
125 + Config["riebeckite.config.ts"]
126 + Vault["vault/<br/>Obsidian content"]
127 +
128 + Site --> Config
129 + Config -->|"content.directory = ../vault"| Vault
130 + ```
131 +
132 + The site and vault roles stay separate:
133 +
134 + ```text
135 + site/
136 + → application
137 +
138 + vault/
139 + → source content
140 + ```
141 +
142 + The vault does not need to be the Vite application root.
143 +
144 + With this layout, the site configuration is explicit and portable:
145 +
146 + ```ts
147 + // site/riebeckite.config.ts
148 + import { defineConfig } from "@riebeckite/core";
149 + import { attachment } from "@riebeckite/plugin-attachment";
150 + import { media } from "@riebeckite/plugin-media";
151 + import { obsidianMarkdown } from "@riebeckite/plugin-obsidian-markdown";
152 +
153 + export default defineConfig({
154 + site: { title: "My notes" },
155 + content: {
156 + directory: "../vault",
157 + exclude: [".obsidian/**", "Templates/**"],
158 + },
159 + plugins: [obsidianMarkdown(), media(), attachment()],
160 + });
161 + ```
162 +
163 + In this layout:
164 +
165 + ```text
166 + appRoot
167 + = workspace/site
168 +
169 + content.directory
170 + = ../vault
171 +
172 + contentRoot
173 + = workspace/vault
174 + ```
175 +
176 + An absolute `directory` is also valid:
177 +
178 + ```ts
179 + content: {
180 + directory: "C:/Users/example/Documents/vault",
181 + }
182 + ```
183 +
184 + but an absolute path stops working when the location changes between developer
185 + machines and CI, so a path relative to the site is normally preferred. Do not
186 + derive the value with `process.cwd()`, and do not make `appRoot` point to the
187 + vault. The vault is source data; Vite's application root must remain the site.
188 +
189 + ## Not depending on process.cwd()
190 +
191 + Avoid building the content directory like this:
192 +
193 + ```ts
194 + directory: path.resolve(
195 + process.cwd(),
196 + "../vault",
197 + )
198 + ```
199 +
200 + The result changes depending on where the CLI is invoked. You also do not need:
201 +
202 + ```text
203 + appRoot = Vault
204 + ```
205 +
206 + The vault is **source data**; `appRoot` is the **site application**:
207 +
208 + ```mermaid
209 + flowchart LR
210 + Vault["Vault<br/>source data"]
211 + Site["Site<br/>application"]
212 + Build["Riebeckite"]
213 +
214 + Vault --> Build
215 + Site --> Build
216 +
217 + Build --> Output["Generated site"]
218 + ```
219 +
220 + Keep this boundary.
221 +
222 + ## Application-side content access
223 +
224 + The HonoX integration resolves the content root automatically and exposes the
225 + resolved values as framework-owned modules. A site imports them instead of
226 + re-reading `riebeckite.config.ts`:
227 +
228 + ```ts
229 + import { config } from "virtual:riebeckite/config";
230 + import { content } from "virtual:riebeckite/content";
231 + ```
232 +
233 + `config.content.directory` is already absolute, and `content` is a ready
234 + `ContentManager` bound to it, so there is no second resolution step and no
235 + per-site config copy to keep in sync. The `riebeckiteVite()` plugin resolves
236 + these modules. A script that runs outside Vite (for example a Node script
237 + started with `tsx`) can resolve the same values with `resolveHonoxConfig` from
238 + `@riebeckite/honox/runtime`:
239 +
240 + ```ts
241 + import { fileURLToPath } from "node:url";
242 + import { resolveConfigModule } from "@riebeckite/core";
243 + import { resolveHonoxConfig } from "@riebeckite/honox/runtime";
244 + import * as rawConfigModule from "../riebeckite.config";
245 +
246 + const appRoot = fileURLToPath(new URL("../", import.meta.url));
247 + export const config = resolveHonoxConfig(resolveConfigModule(rawConfigModule), appRoot);
248 + ```
249 +
250 + ```mermaid
251 + flowchart LR
252 + Relative["../vault"]
253 + Resolve["resolve once from appRoot"]
254 + Absolute["C:/.../vault"]
255 + Manager["ContentManager"]
256 +
257 + Relative --> Resolve
258 + Resolve --> Absolute
259 + Absolute --> Manager
260 + ```
261 +
262 + If `content.source` is configured, it replaces the filesystem reader; do not use
263 + it as a second reader for the same vault.
264 +
265 + ## Placing the config outside the site
266 +
267 + Normally:
268 +
269 + ```text
270 + appRoot
271 + = configRoot
272 + ```
273 +
274 + You can deliberately place `riebeckite.config.ts` in another directory by
275 + passing `configRoot` to `riebeckiteVite()`.
276 +
277 + The roles do not change:
278 +
279 + ```text
280 + appRoot
281 + → site application
282 +
283 + configRoot
284 + → config
285 +
286 + contentRoot
287 + → content / vault
288 + ```
289 +
290 + In particular, do not change `appRoot` to point at the vault. A relative
291 + `content.directory` is still resolved against **`appRoot`**, not `configRoot`.
292 +