nibula 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/Deploy.md CHANGED
@@ -1,221 +1,279 @@
1
1
  # Deploy
2
2
 
3
- Nibula builds a static site into `out`. Publishing means uploading that folder to
4
- a web server. What you need depends on **where** you publish and **which
5
- backend** you chose (see `docs/Backend.md`).
3
+ Nibula builds your finished site into a folder called `out`. Publishing means
4
+ copying that folder onto a web server so people can reach it.
6
5
 
7
- ## The one constraint that decides everything
6
+ How you do that depends on two things: **where** you are publishing, and **which
7
+ backend** you picked when you created the project (see `docs/Backend.md`).
8
8
 
9
- The two backends run in fundamentally different ways:
9
+ ## Start here
10
10
 
11
- - **PHP** is executed **per request** by the web server through PHP-FPM. There is
12
- no process to keep alive — the server starts it, it answers, it exits.
13
- - **Node** **is** the server: a long-running process listening on
14
- `127.0.0.1:3000`, which the web server reverse-proxies `/api` to. Something has
15
- to start it and keep it alive across reboots and crashes.
11
+ If you don't know which case you are in, answer this first:
16
12
 
17
- This is why **shared hosting cannot run the Node backend**. On Aruba, cPanel and
18
- similar, you upload files into a web root — you have no shell to start a daemon,
19
- no systemd, and any process you did manage to launch would be killed. If your
20
- host is shared hosting, choose PHP. It is not a limitation of Nibula.
13
+ **Did you choose the PHP backend or the Node backend?**
14
+
15
+ - **PHP** → almost anywhere works, including cheap hosting plans. Jump to
16
+ *Shared hosting* or *Windows hosting*.
17
+ - **Node** → you need a server you control. Jump to *Your own server*.
18
+ - **No `/api` endpoints at all?** Your site is fully static. Anything works,
19
+ including free hosting like Netlify or GitHub Pages.
20
+
21
+ ## Why the backend decides everything
22
+
23
+ The two backends run in completely different ways, and this is the one thing
24
+ worth understanding before you pick a host.
25
+
26
+ **PHP** runs **once per visit**. A request arrives, the web server starts PHP,
27
+ PHP answers, PHP exits. Nothing stays running, so there is nothing to look after.
28
+
29
+ **Node is itself a server**. It is a program that must stay running all the time,
30
+ listening in the background, while the web server forwards `/api` requests to it.
31
+ Somebody has to start that program and restart it if the machine reboots or the
32
+ program crashes.
33
+
34
+ That is why **cheap shared hosting cannot run the Node backend**. On plans like
35
+ Aruba or cPanel you upload files into a folder and that's all you can do — there
36
+ is no way to start a program and keep it alive. If that's your host, choose PHP.
37
+ It isn't a limitation of Nibula.
38
+
39
+ > **Shared hosting** means a plan where you share a server with other customers
40
+ > and only get a folder to upload into. **A VPS** is a whole machine that is
41
+ > yours, where you can install and run whatever you want.
21
42
 
22
43
  ## Where you can publish
23
44
 
24
45
  | Target | Frontend | PHP backend | Node backend |
25
46
  |---|---|---|---|
26
- | Shared hosting (Apache — Aruba, cPanel) | ✅ | ✅ | ❌ no persistent process |
27
- | Windows hosting (IIS) | ✅ | ✅ FastCGI | ⚠️ needs ARR reverse proxy |
47
+ | Shared hosting (Apache — Aruba, cPanel) | ✅ | ✅ | ❌ no program can stay running |
48
+ | Windows hosting (IIS) | ✅ | ✅ | ⚠️ only with the ARR module |
28
49
  | Your own VPS (Nginx on Ubuntu) | ✅ | ✅ | ✅ |
29
50
  | Static hosting (Netlify, Vercel, GitHub Pages) | ✅ | ❌ | ❌ |
30
51
 
31
- Static hosting serves files and nothing else — fine if your site has no `/api`
32
- endpoints, unusable otherwise.
52
+ Static hosting serves files and nothing else. Perfect if your site has no `/api`
53
+ endpoints, unusable if it does.
33
54
 
34
- ## Requirements
55
+ ## What you need
35
56
 
36
- **PHP backend:** a server with PHP support and Composer for the backend
57
+ **PHP backend:** a host with PHP support, and Composer to install the backend's
37
58
  dependencies.
38
59
 
39
- **Node backend:** Node.js 18+ and shell access to run a persistent process — a
40
- VPS you control.
60
+ **Node backend:** Node.js 18 or newer, and command-line access to the server so
61
+ you can start the backend program.
41
62
 
42
63
  ## Editing the server config
43
64
 
44
- `.htaccess` and `web.config` are in the root of `out`, ready to upload. To change
45
- a rule, edit the source in `src/frontend/hosting/` — a change made directly in
46
- `out` is lost on the next build.
65
+ Every server needs a configuration file telling it how to behave — which page to
66
+ show on a 404, which files to keep private, where to send `/api` requests. Nibula
67
+ writes these for you.
68
+
69
+ `.htaccess` (Apache) and `web.config` (IIS) are already inside `out`, ready to
70
+ upload. To change a rule, edit the original in `src/frontend/hosting/` — anything
71
+ you change directly inside `out` is erased the next time you build.
47
72
 
48
- `nginx.conf` works differently: it sits in the project root and is **not** part
49
- of `out`, because nginx reads no per-directory config. You install it on the
50
- server by hand, as described below.
73
+ `nginx.conf` is different. It sits in your project root and is **not** copied into
74
+ `out`, because Nginx does not read config files from the site folder. You install
75
+ it on the server yourself, as described below.
51
76
 
52
77
  ---
53
78
 
54
79
  ## Shared hosting (Apache)
55
80
 
56
- Upload the **contents of `out`** to the web root (often `htdocs`, `public_html`
57
- or `www`). Done — `.htaccess` is already inside `out` and Apache reads it
58
- automatically: directory listing off, 403/404 → `/404.html`, sensitive files
59
- blocked, backend source sealed, `/api/*` routed.
81
+ Upload **the contents of `out`** — not the folder itself — into your web root.
82
+ Depending on the host it is called `htdocs`, `public_html` or `www`.
60
83
 
61
- **Backend routing:** if `mod_proxy` is available, `/api` is proxied to Node on
62
- `127.0.0.1:3000`; if not — the usual case on shared hosting — `/api` is
63
- rewritten to the PHP front controller. To force PHP on a proxy-enabled server,
64
- comment the `mod_proxy` block in `.htaccess`.
84
+ That's it. `.htaccess` is already in there and Apache reads it automatically:
85
+ folder listings are off, errors show your `/404.html`, sensitive files are
86
+ blocked, the backend source is sealed off, and `/api/*` is routed.
87
+
88
+ **How `/api` gets routed:** if the server has a module called `mod_proxy`, `/api`
89
+ is forwarded to Node. If it doesn't — the usual case on shared hosting — `/api`
90
+ goes to PHP instead. To force PHP on a server that does have `mod_proxy`, comment
91
+ out the `mod_proxy` block in `.htaccess`.
65
92
 
66
93
  ## Windows hosting (IIS)
67
94
 
68
- Same idea: upload the contents of `out`, `web.config` is already there and read
69
- automatically.
95
+ Same as above: upload the contents of `out`. `web.config` is already there and
96
+ read automatically.
70
97
 
71
- **Backend routing:** `/api` is rewritten to the PHP front controller. To use the
72
- Node backend instead, uncomment the `ApiToNode` rule in `web.config` and delete
73
- `ApiToPhp` — this needs the ARR and URL Rewrite modules with proxying enabled,
74
- so a server you control, not shared hosting.
98
+ **How `/api` gets routed:** to PHP, by default. To use Node instead, uncomment
99
+ the `ApiToNode` rule in `web.config` and delete `ApiToPhp`. This only works if
100
+ the server has the ARR and URL Rewrite modules with proxying turned on — which
101
+ means a server you control, not shared hosting.
75
102
 
76
- > ⚠️ **If you chose the Node backend and your host has no ARR** (Aruba and other
77
- > Windows shared hosting), don't upload the `backend` folder at all. The static
78
- > site works, and `/api/*` answers with your `404.html`. Upload it and every
79
- > `/api` request returns an empty 404 instead: IIS rewrites it onto the PHP
80
- > front controller, which a Node project doesn't contain.
103
+ > ⚠️ **If you chose Node and your host has no ARR** (Aruba and most Windows
104
+ > shared hosting), do **not** upload the `backend` folder. Without it the static
105
+ > site works fine and `/api/*` simply shows your `404.html`. Upload it and every
106
+ > `/api` request returns an empty 404 instead, because IIS sends the request to
107
+ > the PHP front controller — a file a Node project doesn't have.
81
108
 
82
109
  ---
83
110
 
84
111
  ## Your own server (Nginx on a VPS)
85
112
 
86
- Nginx doesn't read per-directory config files — the whole site lives in one file.
87
- Nibula ships `nginx.conf` in the project root as a ready-to-use site file that
88
- **covers both backends without edits**: `/api` goes to Node, and if Node is
89
- unreachable the request falls through to PHP automatically.
113
+ Nginx doesn't read config files from the site folder: the whole site is described
114
+ in one file kept elsewhere on the machine. Nibula ships that file, ready to use,
115
+ as `nginx.conf` in your project root.
116
+
117
+ It **handles both backends with no edits**: `/api` goes to Node, and if Node
118
+ isn't running the request falls through to PHP automatically.
119
+
120
+ Upload the contents of `out` to `/var/www/SITE_FOLDER`, then work through the
121
+ steps below.
90
122
 
91
- Upload the contents of `out` to `/var/www/SITE_FOLDER`, then follow the steps
92
- below.
123
+ ### 1. Fill in the placeholders
93
124
 
94
- ### 1. Replace
125
+ Open `nginx.conf` on your computer and replace these four words with your own
126
+ values:
95
127
 
96
- | Placeholder | Where |
128
+ | Placeholder | What to put there |
97
129
  |---|---|
98
- | `YOUR_DOMAIN` | `server_name`, twice (port 80 and 443) |
99
- | `SITE_FOLDER` | document root under `/var/www/` |
100
- | `YOUR_CERTIFICATE` | dir under `/etc/letsencrypt/live/` |
101
- | `SITE_NAME` | file name in the commands below |
130
+ | `YOUR_DOMAIN` | your domain — appears twice, once for port 80 and once for 443 |
131
+ | `SITE_FOLDER` | the folder name you used under `/var/www/` |
132
+ | `YOUR_CERTIFICATE` | the folder name under `/etc/letsencrypt/live/` |
133
+ | `SITE_NAME` | the file name you'll use in the commands below — pick anything |
102
134
 
103
- ### 2. Install (skip if already present)
135
+ ### 2. Install what's missing
136
+
137
+ Skip anything already installed.
104
138
 
105
139
  ```bash
106
140
  sudo apt update
107
141
  sudo apt install -y nginx certbot python3-certbot-nginx
108
142
  ```
109
143
 
110
- If you are using PHP as backend:
144
+ With the PHP backend, also:
111
145
 
112
146
  ```bash
113
147
  sudo apt install -y php-fpm
114
148
  ```
115
149
 
116
- ### 3. Certificate
150
+ ### 3. Get an HTTPS certificate
151
+
152
+ This is what makes your site load over `https://` instead of `http://`.
153
+ Certificates are free and last 90 days; the second command renews them
154
+ automatically.
117
155
 
118
156
  ```bash
119
157
  sudo certbot certonly --nginx --cert-name SITE_NAME -d YOUR_DOMAIN
120
- sudo systemctl enable --now certbot.timer # auto-renew every 90 days
158
+ sudo systemctl enable --now certbot.timer
121
159
  ```
122
160
 
123
- ### 4. Install the site
161
+ ### 4. Install the site file
124
162
 
125
163
  ```bash
126
- # Move to nginx folders
164
+ # Go to the folder where Nginx keeps site files
127
165
  cd /etc/nginx/sites-available
128
166
 
129
- # Create and write a new file
167
+ # Create the file and paste in the contents of your edited nginx.conf
130
168
  sudo nano SITE_NAME
131
- # CTRL + O, ENTER, CTRL + X
169
+ # Paste, then: CTRL + O, ENTER, CTRL + X
132
170
 
133
- # Copy the config to sites-enabled
171
+ # Switch the site on by copying it into sites-enabled
134
172
  sudo cp SITE_NAME ../sites-enabled/
135
173
 
136
- # Test and reload the nginx configuration
174
+ # Check the config for mistakes, then apply it
137
175
  sudo nginx -t && sudo systemctl reload nginx
138
176
  ```
139
177
 
140
- ### If it fails
178
+ If `nginx -t` reports an error, fix it before reloading — Nginx keeps running on
179
+ the old config until the reload succeeds, so your site stays up.
141
180
 
142
- | Symptom | Cause |
181
+ ### If something goes wrong
182
+
183
+ | What you see | What it usually means |
143
184
  |---|---|
144
- | `cannot load certificate ... Permission denied` | `nginx -t` needs sudo |
185
+ | `cannot load certificate ... Permission denied` | you ran `nginx -t` without `sudo` |
145
186
  | `cannot load certificate ... No such file` | wrong certificate name |
146
- | `no alternative certificate subject name` | certificate doesn't cover domain |
147
- | `unknown directive "http2"` | nginx < 1.25, check with `nginx -v` |
148
- | NXDOMAIN / timeout during certbot | DNS not pointing here |
149
- | 500 instead of the 404 page | `404.html` missing |
150
- | 502 on `/api`, Node deployed | Node process not running |
151
- | 502 on `/api`, PHP deployed | wrong php-fpm socket path |
187
+ | `no alternative certificate subject name` | the certificate doesn't cover this domain |
188
+ | `unknown directive "http2"` | Nginx older than 1.25 — check with `nginx -v` |
189
+ | NXDOMAIN or a timeout during certbot | your domain's DNS isn't pointing at this server yet |
190
+ | A blank 500 instead of your 404 page | `404.html` is missing from the upload |
191
+ | 502 on `/api`, Node backend | the Node program isn't running |
192
+ | 502 on `/api`, PHP backend | wrong php-fpm socket path in the config |
152
193
 
153
194
  ---
154
195
 
155
196
  ## Starting the Node backend on the server
156
197
 
157
- After uploading `out`:
198
+ Only for the Node backend. After uploading `out`:
199
+
200
+ **1. Install its dependencies**
201
+
202
+ ```bash
203
+ cd /var/www/SITE_FOLDER/backend
204
+ npm install
205
+ ```
206
+
207
+ **2. Create its config file**
158
208
 
159
- 1. Install the backend's runtime deps:
160
- ```bash
161
- cd /var/www/SITE_FOLDER/backend
162
- npm install
163
- ```
209
+ Make sure `config.js` exists in `out/backend/`. If it doesn't, copy
210
+ `example.config.js` to `config.js` and fill in your values.
164
211
 
165
- 2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js` if
166
- missing) and fill in your values.
212
+ **3. Try it out first**
167
213
 
168
- 3. Start it in a new screen:
169
- ```bash
170
- screen -S node-backend
171
- # In the new screen terminal:
172
- sudo node /var/www/SITE_FOLDER/backend/_core/index.js
173
- curl http://127.0.0.1:3000/api/example-public
174
- ```
214
+ `screen` lets you start a program, walk away, and come back to it later. Use it
215
+ to check the backend actually works before setting it up properly.
175
216
 
176
- 4. Exit the screen without closing the process with `CTRL + A`, `D`. To resume
177
- the screen type `screen -r node-backend`.
217
+ ```bash
218
+ screen -S node-backend
219
+ # Inside the new screen:
220
+ sudo node /var/www/SITE_FOLDER/backend/_core/index.js
221
+ curl http://127.0.0.1:3000/api/example-public
222
+ ```
178
223
 
179
- 5. For production use **systemd**, which also restarts on crash and reboot.
180
- Create `/etc/systemd/system/backend-node.service`:
224
+ If `curl` prints a JSON response, the backend is alive. Press `CTRL + A` then `D`
225
+ to leave the screen without stopping the program; `screen -r node-backend` brings
226
+ you back.
181
227
 
182
- ```ini
183
- [Unit]
184
- Description=Nibula Node backend
185
- After=network.target
228
+ **4. Then set it up for real**
186
229
 
187
- [Service]
188
- Type=simple
189
- User=www-data
190
- WorkingDirectory=/var/www/SITE_FOLDER/backend
191
- ExecStart=/usr/bin/node /var/www/SITE_FOLDER/backend/_core/index.js
192
- Restart=always
193
- RestartSec=5
230
+ `screen` is for testing — the program dies if the server reboots. For a live site
231
+ use **systemd**, the part of Linux that manages background programs. It starts
232
+ your backend at boot and restarts it if it crashes.
194
233
 
195
- [Install]
196
- WantedBy=multi-user.target
197
- ```
234
+ Create `/etc/systemd/system/backend-node.service`:
198
235
 
199
- Adjust `WorkingDirectory` and the `node` path (`which node`), then:
236
+ ```ini
237
+ [Unit]
238
+ Description=Nibula Node backend
239
+ After=network.target
240
+
241
+ [Service]
242
+ Type=simple
243
+ User=www-data
244
+ WorkingDirectory=/var/www/SITE_FOLDER/backend
245
+ ExecStart=/usr/bin/node /var/www/SITE_FOLDER/backend/_core/index.js
246
+ Restart=always
247
+ RestartSec=5
248
+
249
+ [Install]
250
+ WantedBy=multi-user.target
251
+ ```
252
+
253
+ Adjust `WorkingDirectory`, and check the path to `node` with `which node`. Then:
254
+
255
+ ```bash
256
+ sudo systemctl daemon-reload
257
+ sudo systemctl enable --now backend-node
258
+ sudo systemctl status backend-node
259
+ ```
200
260
 
201
- ```bash
202
- sudo systemctl daemon-reload
203
- sudo systemctl enable --now backend-node
204
- sudo systemctl status backend-node
205
- ```
261
+ The last command should say `active (running)`.
206
262
 
207
- (`pm2` works too: `pm2 start _core/index.js --name backend-node`.)
263
+ (If you already use `pm2`, that works too:
264
+ `pm2 start _core/index.js --name backend-node`.)
208
265
 
209
- ### Environment variables
266
+ ### Settings the Node backend reads at startup
210
267
 
211
- Read by the Node backend at startup. Set them in the systemd unit with
212
- `Environment=KEY=value`.
268
+ These are set in the systemd file, on their own lines, as
269
+ `Environment=KEY=value`. All three have sensible defaults — you usually don't
270
+ need to touch them.
213
271
 
214
- | Variable | Default | Purpose |
272
+ | Setting | Default | What it does |
215
273
  |---|---|---|
216
- | `PORT` | `3000` | Port Node listens on (must match the proxy target in `nginx.conf`) |
217
- | `HOST` | `127.0.0.1` | Bind address — keep it local; the web server is the public face |
218
- | `DOCUMENT_ROOT` | auto (`SITE_FOLDER/`) | Where `404.html` lives; detected as the folder above `backend/` |
274
+ | `PORT` | `3000` | the port Node listens on — must match the one in `nginx.conf` |
275
+ | `HOST` | `127.0.0.1` | keep this as-is: it means "local only", so Node is reachable through the web server and not directly from the internet |
276
+ | `DOCUMENT_ROOT` | detected automatically | where `404.html` lives — the folder above `backend/` |
219
277
 
220
- Error verbosity is **not** an environment variable: it is the `APP_ENV` key in
221
- `config.js` / `config.php`. See `docs/Backend.md`.
278
+ > Error detail is **not** set here. That's the `APP_ENV` key inside `config.js` /
279
+ > `config.php` — see `docs/Backend.md`.
@@ -1,8 +1,22 @@
1
1
  # Head & SEO
2
2
 
3
- Global settings live in `src/frontend/data/site.json` and are available everywhere via `{{ site.* }}`.
3
+ The `<head>` of an HTML page holds the information *about* the page rather than
4
+ its content: the title shown in the browser tab, the description search engines
5
+ display, the image that appears when someone shares the link. Nibula writes all
6
+ of it for you — you just fill in the values.
4
7
 
5
- ## Global fields
8
+ There are two files to fill in:
9
+
10
+ - **`src/frontend/data/site.json`** — settings for the whole site
11
+ - **`src/frontend/data/pages.json`** — settings for one page at a time
12
+
13
+ A page uses its own value when it has one, and falls back to the site-wide value
14
+ when it doesn't. So filling in `site.json` well means most pages need nothing.
15
+
16
+ ## Site-wide settings
17
+
18
+ Everything in `site.json` is available anywhere in your templates as
19
+ `{{ site.something }}`.
6
20
 
7
21
  ```json
8
22
  {
@@ -29,24 +43,26 @@ Global settings live in `src/frontend/data/site.json` and are available everywhe
29
43
  }
30
44
  ```
31
45
 
32
- | Field | Purpose |
46
+ | Field | What it's for |
33
47
  |---|---|
34
- | `site_name` | Brand name (used in meta tags and JSON-LD) |
35
- | `title` | Default page title — fallback when a page has no `seo.title` |
36
- | `description` | Default meta description — fallback for pages |
37
- | `keywords` | Default meta keywords — fallback for pages |
38
- | `domain` / `url` | Canonical URLs, `og:url`, JSON-LD |
39
- | `lang` | HTML `lang` attribute |
40
- | `author` | Meta author and JSON-LD author |
41
- | `theme` | Site color scheme (`light` / `dark`) — sets `data-theme`, `data-bs-theme` and `color-scheme` |
42
- | `theme_color` | Browser UI / PWA bar color — should match the chosen `theme` |
43
- | `logo` | Path to the logo, also used as social image |
44
- | `legal.privacy` / `cookie` / `terms` | Legal page URLs |
45
- | `legal.copyright.year` / `text` | Footer copyright |
46
-
47
- ## Per-page SEO and CDN
48
-
49
- Per-page settings live in `src/frontend/data/pages.json`, available via `{{ pages.* }}`. Each page is keyed by its camelCase `title` from the front matter, at the root of the file:
48
+ | `site_name` | Your brand or project name |
49
+ | `title` | Default page title — used by any page that doesn't set its own |
50
+ | `description` | Default description — same idea |
51
+ | `keywords` | Default keywords — same idea |
52
+ | `domain` / `url` | Used to build full page addresses for search engines and social previews |
53
+ | `lang` | The language of your site (`en`, `it`, ...) |
54
+ | `author` | Your name |
55
+ | `theme` | Colour scheme of the site: `light` or `dark` |
56
+ | `theme_color` | Colour of the browser bar on phones — should match your theme |
57
+ | `logo` | Path to your logo, also used as the image in social previews |
58
+ | `legal.privacy` / `cookie` / `terms` | Addresses of your legal pages — the footer links to them |
59
+ | `legal.copyright.year` / `text` | The copyright line in the footer |
60
+
61
+ ## Per-page settings
62
+
63
+ `pages.json` holds one entry per page, and the assistant creates it for you when
64
+ you add a page. Each entry is keyed by the page's `title` from its front matter —
65
+ the camelCase one.
50
66
 
51
67
  ```json
52
68
  {
@@ -66,20 +82,23 @@ Per-page settings live in `src/frontend/data/pages.json`, available via `{{ page
66
82
  }
67
83
  ```
68
84
 
69
- | Field | Purpose | If empty / absent |
85
+ | Field | What it's for | If you leave it empty |
70
86
  |---|---|---|
71
- | `seo.title` | Page title, and the page's label in `llms.txt` | Falls back to global `title` |
72
- | `seo.description` | Meta description, and the page's summary in `llms.txt` | Falls back to global `description` |
73
- | `seo.keywords` | Meta keywords | Falls back to global `keywords` |
74
- | `seo.noindex` | If `true`, emits `<meta name="robots" content="noindex, follow">` and drops the page from both the sitemap and `llms.txt` | Page is indexable |
75
- | `seo.canonical` | Absolute canonical URL override | Computed as `url + page.url` |
76
- | `cdn.css` / `cdn.js` | Extra per-page CDN links | No extra links loaded |
87
+ | `seo.title` | The page's title, shown in the browser tab and in search results | Uses the site-wide `title` |
88
+ | `seo.description` | The short summary search engines show under the title | Uses the site-wide `description` |
89
+ | `seo.keywords` | Keywords for this page | Uses the site-wide `keywords` |
90
+ | `seo.noindex` | Set to `true` to ask search engines **not** to list this page | The page can be found normally |
91
+ | `seo.canonical` | The one official address of this page, if the same content is reachable at more than one URL | Worked out automatically |
92
+ | `cdn.css` / `cdn.js` | Extra stylesheets or scripts loaded from the internet, on this page only | Nothing extra is loaded |
77
93
 
78
- The assistant creates the stub entry when you add a page; a page left with the placeholder description will advertise itself that way to AI crawlers.
94
+ > ⚠️ The assistant fills new entries with placeholder text like `"Description"`.
95
+ > Replace it — a page left that way tells search engines and AI crawlers that its
96
+ > description is literally "Description".
79
97
 
80
- ## Fallback logic
98
+ ### How the fallback works
81
99
 
82
- Global values are defaults, not duplicates. A page value is used when present; otherwise the global one applies:
100
+ Site-wide values are defaults, not duplicates. You don't need to repeat them per
101
+ page. This is what the template actually does:
83
102
 
84
103
  ```njk
85
104
  {{ pageData.seo.title or title or site.title }}
@@ -87,11 +106,15 @@ Global values are defaults, not duplicates. A page value is used when present; o
87
106
  {{ pageData.seo.keywords or site.keywords }}
88
107
  ```
89
108
 
90
- `noindex` and `canonical` have no global default: `noindex` defaults to indexable behavior, `canonical` is computed from the URL. They exist per-page only.
109
+ Read it as: use the page's value; if there isn't one, use the site's.
110
+
111
+ `noindex` and `canonical` work per-page only — there's no site-wide version,
112
+ because "hide the whole site from search" and "one address for the whole site"
113
+ aren't things you'd want.
91
114
 
92
115
  ## Theme
93
116
 
94
- `theme` is a single fixed value that drives the whole site:
117
+ One value in `site.json` controls the colour scheme everywhere:
95
118
 
96
119
  ```njk
97
120
  <html data-theme="{{ site.theme }}" data-bs-theme="{{ site.theme }}">
@@ -99,20 +122,24 @@ Global values are defaults, not duplicates. A page value is used when present; o
99
122
  <meta name="theme-color" content="{{ site.theme_color }}">
100
123
  ```
101
124
 
102
- | Attribute | Purpose |
125
+ | What it sets | Why it's there |
103
126
  |---|---|
104
- | `data-theme` | Your own hook — target it in SCSS to define theme variables |
105
- | `data-bs-theme` | Bootstrap's color mode. Ignored (harmless) if you use another framework |
106
- | `color-scheme` | Standard CSS — makes scrollbars, form controls and autofill follow the theme |
107
- | `theme-color` | Color of the browser UI bar on mobile |
127
+ | `data-theme` | Your own hook — target it in your SCSS to define theme colours |
128
+ | `data-bs-theme` | Bootstrap's dark mode switch. Harmlessly ignored with other frameworks |
129
+ | `color-scheme` | Standard CSS — makes scrollbars, form fields and autofill match your theme |
130
+ | `theme-color` | The colour of the browser bar on phones |
108
131
 
109
- Keep `theme` and `theme_color` consistent: a `dark` theme with a white `theme_color` gives a white browser bar over a dark page.
132
+ Keep `theme` and `theme_color` in agreement: a dark theme with a white
133
+ `theme_color` gives a white browser bar sitting on top of a dark page.
110
134
 
111
- See the SCSS docs for how to hook your variables to `data-theme`.
135
+ See the **Styling with SCSS** DOC file for how to hook your own colours to
136
+ `data-theme`.
112
137
 
113
138
  ## Favicon
114
139
 
115
- The three favicon tags are hardcoded in `base.njk`. To change the icons, replace the files in `src/frontend/assets/brand/` keeping the same names:
140
+ The favicon is the small icon in the browser tab. The three tags are written
141
+ directly in `base.njk`. To change the icons, replace the files in
142
+ `src/frontend/assets/brand/` **keeping the same names**:
116
143
 
117
144
  ```html
118
145
  <link rel="icon" type="image/svg+xml" href="{{ '/assets/brand/favicon.svg' | url }}">
@@ -120,37 +147,52 @@ The three favicon tags are hardcoded in `base.njk`. To change the icons, replace
120
147
  <link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/brand/apple-touch-icon.png' | url }}">
121
148
  ```
122
149
 
123
- | File | Purpose |
150
+ | File | What uses it |
124
151
  |---|---|
125
- | `favicon.svg` | Modern browsers, scales to any size |
126
- | `favicon-32.png` | Fallback for older browsers and some crawlers |
127
- | `apple-touch-icon.png` | iOS home screen icon — 180×180, must be opaque (iOS renders transparency as black) |
152
+ | `favicon.svg` | Modern browsers — scales to any size |
153
+ | `favicon-32.png` | Older browsers and some crawlers |
154
+ | `apple-touch-icon.png` | The icon iOS uses when someone saves your site to their home screen. Must be 180×180 and **not** transparent — iOS turns transparency into black |
128
155
 
129
- > ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of the page as the home screen icon.
156
+ > ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of your page as the
157
+ > home screen icon instead.
130
158
 
131
- ## Where the tags are written
159
+ ## Where all this is written
132
160
 
133
- All of the above is rendered by `src/frontend/layouts/base.njk`, which reads the page's record with:
161
+ Everything above is produced by `src/frontend/layouts/base.njk`, which looks up
162
+ the current page's entry like this:
134
163
 
135
164
  ```njk
136
165
  {% set pageKey = title %}
137
166
  {% set pageData = pages[pageKey] %}
138
167
  ```
139
168
 
140
- Edit the template when you need a tag the boilerplate doesn't cover — a font `<link>`, an analytics snippet. For everything listed here, change the values in `site.json` and `pages.json` instead.
169
+ You only need to edit `base.njk` for something the boilerplate doesn't cover — a
170
+ web font, an analytics snippet. For everything documented here, change the values
171
+ in `site.json` and `pages.json` instead.
141
172
 
142
- > ⚠️ Asset paths in `base.njk` go through the `url` filter. It makes them work when the site is published under a subpath — GitHub Pages being the usual case. Removing it 404s every stylesheet and script there.
173
+ > ⚠️ Paths in `base.njk` go through `| url`. That filter makes them work when the
174
+ > site is published in a subfolder rather than at the root of a domain — GitHub
175
+ > Pages being the common case. Remove it and every stylesheet and script breaks
176
+ > there.
143
177
 
144
- ## AI & SEO bots
178
+ ## Files for search engines and AI
145
179
 
146
- `llms.txt`, `robots.txt` and `sitemap.xml` are generated from `.njk` templates in `src/frontend/indexing/` and are publicly reachable (they must not be blocked by the server config).
180
+ Three files are generated automatically and published with your site:
147
181
 
148
- | File | Purpose | Reachable at |
182
+ | File | What it's for | Reachable at |
149
183
  |---|---|---|
150
- | `llms.njk` | Describes the site and lists its pages for AI models | `yoursite.com/llms.txt` |
151
- | `robots.njk` | Controls search-engine crawling | `yoursite.com/robots.txt` |
152
- | `sitemap.njk` | Lists indexable pages | `yoursite.com/sitemap.xml` |
184
+ | `llms.txt` | Describes your site and lists its pages, for AI models | `yoursite.com/llms.txt` |
185
+ | `robots.txt` | Tells search engines what they may and may not crawl | `yoursite.com/robots.txt` |
186
+ | `sitemap.xml` | Lists your pages so search engines find them all | `yoursite.com/sitemap.xml` |
187
+
188
+ They are built from templates in `src/frontend/indexing/`. `llms.txt` and
189
+ `sitemap.xml` build their page lists the same way: they go through every page and
190
+ leave out drafts, the 404 page, and anything marked `noindex`. Creating a page
191
+ with the assistant is enough for it to appear in both — there is no list to keep
192
+ up to date by hand.
153
193
 
154
- `llms.njk` and `sitemap.njk` build their lists the same way: they walk every page, then skip drafts, the 404 page, and anything with `seo.noindex` set to `true`. Creating a page with the assistant is enough to have it appear in both — there is no separate list to maintain.
194
+ To change the wording or the fixed parts, edit
195
+ `src/frontend/indexing/llms.njk` or `src/frontend/indexing/robots.njk`.
155
196
 
156
- To customize the wording or the fixed sections, edit `src/frontend/indexing/llms.njk` or `src/frontend/indexing/robots.njk` directly.
197
+ > These three files must stay publicly reachable. If you block them in your server
198
+ > config, search engines and AI models can't read them.